@reldens/cms 0.20.0 → 0.23.0
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/README.md +648 -12
- package/admin/reldens-admin-client.css +77 -141
- package/admin/reldens-admin-client.js +108 -133
- package/admin/templates/clear-all-cache-button.html +7 -7
- package/admin/templates/edit.html +7 -0
- package/admin/templates/fields/view/audio.html +7 -0
- package/admin/templates/fields/view/audios.html +8 -0
- package/admin/templates/layout.html +15 -9
- package/admin/templates/list-content.html +4 -2
- package/admin/templates/list.html +24 -8
- package/admin/templates/view.html +21 -0
- package/install/index.html +4 -0
- package/lib/admin-manager/admin-filters-manager.js +177 -0
- package/lib/admin-manager/contents-builder.js +1 -0
- package/lib/admin-manager/default-translations.js +38 -0
- package/lib/admin-manager/router-contents.js +64 -52
- package/lib/admin-manager/router.js +19 -0
- package/lib/dynamic-form-renderer.js +228 -0
- package/lib/dynamic-form-request-handler.js +135 -0
- package/lib/dynamic-form.js +310 -0
- package/lib/frontend/content-renderer.js +178 -0
- package/lib/frontend/entity-access-manager.js +63 -0
- package/lib/frontend/request-processor.js +128 -0
- package/lib/frontend/response-manager.js +54 -0
- package/lib/frontend/template-cache.js +102 -0
- package/lib/frontend/template-resolver.js +111 -0
- package/lib/frontend.js +122 -629
- package/lib/installer.js +2 -1
- package/lib/manager.js +25 -12
- package/lib/search-renderer.js +15 -7
- package/lib/search-request-handler.js +67 -0
- package/lib/search.js +13 -1
- package/lib/template-engine/collections-single-transformer.js +11 -5
- package/lib/template-engine/collections-transformer.js +47 -34
- package/lib/template-engine/entities-transformer.js +3 -2
- package/lib/template-engine/forms-transformer.js +187 -0
- package/lib/template-engine/partials-transformer.js +5 -6
- package/lib/template-engine/system-variables-provider.js +4 -1
- package/lib/template-engine.js +28 -5
- package/lib/template-reloader.js +307 -0
- package/lib/templates-list.js +2 -0
- package/migrations/default-forms.sql +22 -0
- package/package.json +5 -5
- package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
- package/templates/assets/favicons/default/favicon.ico +0 -0
- package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
- package/templates/cms_forms/field_email.html +14 -0
- package/templates/cms_forms/field_number.html +17 -0
- package/templates/cms_forms/field_select.html +15 -0
- package/templates/cms_forms/field_text.html +16 -0
- package/templates/cms_forms/field_textarea.html +13 -0
- package/templates/cms_forms/form.html +22 -0
- package/templates/css/styles.css +4 -0
- package/templates/js/functions.js +144 -0
- package/templates/js/scripts.js +5 -0
- package/templates/page.html +11 -5
- package/templates/partials/pagedCollection.html +1 -1
- package/lib/admin-translations.js +0 -56
- package/templates/favicon.ico +0 -0
- /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
- /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
- /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
- /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
- /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
- /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
- /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
- /package/templates/assets/favicons/{safari-pinned-tab.svg → default/safari-pinned-tab.svg} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - ContentRenderer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
8
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
9
|
+
|
|
10
|
+
class ContentRenderer
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor(props)
|
|
14
|
+
{
|
|
15
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
|
+
this.templateEngine = sc.get(props, 'templateEngine', false);
|
|
17
|
+
this.templateResolver = sc.get(props, 'templateResolver', false);
|
|
18
|
+
this.templateCache = sc.get(props, 'templateCache', false);
|
|
19
|
+
this.metaDefaults = sc.get(props, 'metaDefaults', {});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fetchMetaFields(data)
|
|
23
|
+
{
|
|
24
|
+
if(!sc.isObject(data) || 0 === Object.keys(data).length){
|
|
25
|
+
return this.metaDefaults;
|
|
26
|
+
}
|
|
27
|
+
let result = Object.assign({}, this.metaDefaults);
|
|
28
|
+
for(let key of Object.keys(data)){
|
|
29
|
+
let value = data[key];
|
|
30
|
+
if(null !== value && '' !== value && 'undefined' !== typeof value){
|
|
31
|
+
result[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let titleValue = sc.get(result, 'title', '');
|
|
35
|
+
let metaTitleValue = sc.get(result, 'meta_title', titleValue);
|
|
36
|
+
if(metaTitleValue && '' !== metaTitleValue){
|
|
37
|
+
result.meta_title = metaTitleValue;
|
|
38
|
+
}
|
|
39
|
+
let metaOgTitleValue = sc.get(result, 'meta_og_title', metaTitleValue);
|
|
40
|
+
if(metaOgTitleValue && '' !== metaOgTitleValue){
|
|
41
|
+
result.meta_og_title = metaOgTitleValue;
|
|
42
|
+
}
|
|
43
|
+
let metaDescValue = sc.get(result, 'meta_description', sc.get(result, 'description', ''));
|
|
44
|
+
if(metaDescValue && '' !== metaDescValue){
|
|
45
|
+
result.meta_description = metaDescValue;
|
|
46
|
+
}
|
|
47
|
+
let metaOgDescValue = sc.get(result, 'meta_og_description', metaDescValue);
|
|
48
|
+
if(metaOgDescValue && '' !== metaOgDescValue){
|
|
49
|
+
result.meta_og_description = metaOgDescValue;
|
|
50
|
+
}
|
|
51
|
+
let jsonData = sc.get(result, 'json_data', null);
|
|
52
|
+
if(sc.isString(jsonData)){
|
|
53
|
+
jsonData = sc.toJson(jsonData, {});
|
|
54
|
+
}
|
|
55
|
+
if(!sc.isObject(jsonData)){
|
|
56
|
+
jsonData = {};
|
|
57
|
+
}
|
|
58
|
+
let viewportValue = sc.get(jsonData, 'viewport', this.metaDefaults.viewport);
|
|
59
|
+
if(viewportValue && '' !== viewportValue){
|
|
60
|
+
jsonData.viewport = viewportValue;
|
|
61
|
+
}
|
|
62
|
+
result.json_data = jsonData;
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async generateRouteContent(route, domain, req)
|
|
67
|
+
{
|
|
68
|
+
if(!route.router){
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
let entity = this.dataServer.getEntity(route.router);
|
|
72
|
+
if(!entity){
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
let content = await entity.loadOneWithRelations({route_id: route.id});
|
|
76
|
+
if(!content){
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain, req, route);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async generateTemplateContent(templatePath, domain, req, data = {})
|
|
83
|
+
{
|
|
84
|
+
let template = FileHandler.readFile(templatePath);
|
|
85
|
+
if(!template){
|
|
86
|
+
Logger.error('Failed to read template: ' + templatePath);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return await this.templateEngine.render(
|
|
90
|
+
template,
|
|
91
|
+
data,
|
|
92
|
+
this.templateCache.getPartialsForDomain(domain),
|
|
93
|
+
domain,
|
|
94
|
+
req,
|
|
95
|
+
null,
|
|
96
|
+
null
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async renderWithTemplateContent(content, data, domain, req, route)
|
|
101
|
+
{
|
|
102
|
+
let templateName = sc.get(content, 'template', 'page');
|
|
103
|
+
if(!templateName){
|
|
104
|
+
templateName = 'page';
|
|
105
|
+
}
|
|
106
|
+
let layoutName = sc.get(content, 'layout', '');
|
|
107
|
+
if(!layoutName){
|
|
108
|
+
layoutName = 'default';
|
|
109
|
+
}
|
|
110
|
+
let currentEntityData = Object.assign({}, content, data);
|
|
111
|
+
let layoutContent = await this.processContentWithLayout(
|
|
112
|
+
content,
|
|
113
|
+
data,
|
|
114
|
+
layoutName,
|
|
115
|
+
domain,
|
|
116
|
+
req,
|
|
117
|
+
route,
|
|
118
|
+
currentEntityData
|
|
119
|
+
);
|
|
120
|
+
let templatePath = this.templateResolver.findTemplatePath(templateName, domain);
|
|
121
|
+
if(!templatePath){
|
|
122
|
+
return layoutContent;
|
|
123
|
+
}
|
|
124
|
+
let routerKey = route?.router;
|
|
125
|
+
let categoryName = currentEntityData?.cms_categories?.name;
|
|
126
|
+
let siteHandle = this.templateResolver.resolveDomainToSiteKey(domain)
|
|
127
|
+
+ (routerKey ? ' '+routerKey : '')
|
|
128
|
+
+ (categoryName ? ' cat-'+categoryName : '');
|
|
129
|
+
return await this.generateTemplateContent(
|
|
130
|
+
templatePath,
|
|
131
|
+
domain,
|
|
132
|
+
req,
|
|
133
|
+
Object.assign(
|
|
134
|
+
{},
|
|
135
|
+
this.fetchMetaFields(data),
|
|
136
|
+
{
|
|
137
|
+
content: layoutContent,
|
|
138
|
+
siteHandle
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async processContentWithLayout(content, data, layoutName, domain, req, route, currentEntityData)
|
|
145
|
+
{
|
|
146
|
+
let processedContent = await this.processContent(content, data, domain, req, route, currentEntityData);
|
|
147
|
+
let layoutPath = this.templateResolver.findLayoutPath(layoutName, domain);
|
|
148
|
+
if(!layoutPath){
|
|
149
|
+
return processedContent;
|
|
150
|
+
}
|
|
151
|
+
return await this.generateTemplateContent(
|
|
152
|
+
layoutPath,
|
|
153
|
+
domain,
|
|
154
|
+
req,
|
|
155
|
+
Object.assign({}, data, {content: processedContent})
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async processContent(content, data, domain, req, route, currentEntityData)
|
|
160
|
+
{
|
|
161
|
+
let contentText = sc.get(content, 'content', '');
|
|
162
|
+
if(!contentText){
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
return await this.templateEngine.render(
|
|
166
|
+
contentText,
|
|
167
|
+
data,
|
|
168
|
+
this.templateCache.getPartialsForDomain(domain),
|
|
169
|
+
domain,
|
|
170
|
+
req,
|
|
171
|
+
route,
|
|
172
|
+
currentEntityData
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports.ContentRenderer = ContentRenderer;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - EntityAccessManager
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class EntityAccessManager
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
15
|
+
this.entityAccessCache = new Map();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async loadEntityAccessRules()
|
|
19
|
+
{
|
|
20
|
+
let accessEntity = this.dataServer.getEntity('entitiesAccess');
|
|
21
|
+
if(!accessEntity){
|
|
22
|
+
Logger.warning('Entities Access not found.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
let accessRules = await accessEntity.loadAll();
|
|
26
|
+
for(let rule of accessRules){
|
|
27
|
+
this.entityAccessCache.set(rule.entity_name, rule.is_public);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async isEntityAccessible(entityName)
|
|
32
|
+
{
|
|
33
|
+
if(this.entityAccessCache.has(entityName)){
|
|
34
|
+
return this.entityAccessCache.get(entityName);
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async findEntityByPath(path)
|
|
40
|
+
{
|
|
41
|
+
let pathSegments = path.split('/').filter(segment => '' !== segment);
|
|
42
|
+
if(2 > pathSegments.length){
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
let entityName = pathSegments[0];
|
|
46
|
+
if(!await this.isEntityAccessible(entityName)){
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
let entityId = pathSegments[1];
|
|
50
|
+
let entity = this.dataServer.getEntity(entityName);
|
|
51
|
+
if(!entity){
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
let loadedEntity = await entity.loadByIdWithRelations(entityId);
|
|
55
|
+
if(!loadedEntity){
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return {entity: loadedEntity, entityName};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports.EntityAccessManager = EntityAccessManager;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - RequestProcessor
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class RequestProcessor
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
15
|
+
this.templateResolver = sc.get(props, 'templateResolver', false);
|
|
16
|
+
this.domainMapping = sc.get(props, 'domainMapping', {});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getDomainFromRequest(req)
|
|
20
|
+
{
|
|
21
|
+
let host = req.get('host');
|
|
22
|
+
if(!host){
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return host.split(':')[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolveDomainMapping(domain)
|
|
29
|
+
{
|
|
30
|
+
if(!domain){
|
|
31
|
+
return domain;
|
|
32
|
+
}
|
|
33
|
+
return sc.get(this.domainMapping, domain, domain);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
normalizePathForRouteSearch(path)
|
|
37
|
+
{
|
|
38
|
+
if(!path || '/' === path){
|
|
39
|
+
return '/';
|
|
40
|
+
}
|
|
41
|
+
return path.endsWith('/') ? path.slice(0, -1) : path;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async handleRouteRedirect(route, res)
|
|
45
|
+
{
|
|
46
|
+
let redirectUrl = sc.get(route, 'redirect_url', null);
|
|
47
|
+
if(!redirectUrl){
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
let redirectType = sc.get(route, 'redirect_type', '301');
|
|
51
|
+
let statusCode = '301' === redirectType ? 301 : 302;
|
|
52
|
+
res.redirect(statusCode, redirectUrl);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async findRouteByPath(path, domain)
|
|
57
|
+
{
|
|
58
|
+
let routesEntity = this.dataServer.getEntity('routes');
|
|
59
|
+
if(!routesEntity){
|
|
60
|
+
Logger.error('Routes entity not found in dataServer.');
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
let normalizedPath = this.normalizePathForRouteSearch(path);
|
|
64
|
+
let resolvedDomain = this.resolveDomainMapping(domain);
|
|
65
|
+
let domainFilter = resolvedDomain || null;
|
|
66
|
+
let routeFilters = {path: normalizedPath, enabled: 1};
|
|
67
|
+
let routes = await routesEntity.load(routeFilters);
|
|
68
|
+
let matchingRoute = false;
|
|
69
|
+
let nullDomain = false;
|
|
70
|
+
for(let route of routes){
|
|
71
|
+
if(route.domain === domainFilter){
|
|
72
|
+
matchingRoute = route;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if(!route.domain){
|
|
76
|
+
nullDomain = route;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if(matchingRoute){
|
|
80
|
+
return matchingRoute;
|
|
81
|
+
}
|
|
82
|
+
if(nullDomain){
|
|
83
|
+
return nullDomain;
|
|
84
|
+
}
|
|
85
|
+
if(normalizedPath !== path){
|
|
86
|
+
let routeFiltersWithSlash = {path: path, enabled: 1};
|
|
87
|
+
let routesWithSlash = await routesEntity.load(routeFiltersWithSlash);
|
|
88
|
+
for(let route of routesWithSlash){
|
|
89
|
+
if(route.domain === domainFilter){
|
|
90
|
+
return route;
|
|
91
|
+
}
|
|
92
|
+
if(!route.domain){
|
|
93
|
+
nullDomain = route;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if(nullDomain){
|
|
97
|
+
return nullDomain;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
buildCacheKey(path, req)
|
|
104
|
+
{
|
|
105
|
+
if(!req || !req.query){
|
|
106
|
+
return path;
|
|
107
|
+
}
|
|
108
|
+
for(let key of Object.keys(req.query)){
|
|
109
|
+
if(key.endsWith('-key')){
|
|
110
|
+
let queryString = '';
|
|
111
|
+
for(let qKey of Object.keys(req.query)){
|
|
112
|
+
queryString += (queryString ? '&' : '') + qKey + '=' + req.query[qKey];
|
|
113
|
+
}
|
|
114
|
+
let hash = 0;
|
|
115
|
+
for(let i = 0; i < queryString.length; i++){
|
|
116
|
+
let char = queryString.charCodeAt(i);
|
|
117
|
+
hash = ((hash << 5) - hash) + char;
|
|
118
|
+
hash = hash & hash;
|
|
119
|
+
}
|
|
120
|
+
return path + '_' + Math.abs(hash);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return path;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports.RequestProcessor = RequestProcessor;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - ResponseManager
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class ResponseManager
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
15
|
+
this.contentRenderer = sc.get(props, 'contentRenderer', false);
|
|
16
|
+
this.templateResolver = sc.get(props, 'templateResolver', false);
|
|
17
|
+
this.requestProcessor = sc.get(props, 'requestProcessor', false);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async renderWithCacheHandler(contentGenerator, errorHandler, responseHandler, domain, res, path, req)
|
|
21
|
+
{
|
|
22
|
+
let renderedContent = await contentGenerator();
|
|
23
|
+
if(!renderedContent){
|
|
24
|
+
return await errorHandler();
|
|
25
|
+
}
|
|
26
|
+
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
27
|
+
let cacheKey = this.requestProcessor.buildCacheKey(path, req);
|
|
28
|
+
await this.cacheManager.set(domain, cacheKey, renderedContent);
|
|
29
|
+
}
|
|
30
|
+
return await responseHandler(renderedContent);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async renderNotFound(domain, res, req)
|
|
34
|
+
{
|
|
35
|
+
let notFoundPath = this.templateResolver.findTemplatePath('404', domain);
|
|
36
|
+
if(notFoundPath){
|
|
37
|
+
let content = await this.contentRenderer.generateTemplateContent(notFoundPath, domain, req, {});
|
|
38
|
+
if(content){
|
|
39
|
+
res.status(404);
|
|
40
|
+
return res.send(await this.contentRenderer.renderWithTemplateContent(
|
|
41
|
+
{content},
|
|
42
|
+
{meta_title: 'Page not found'},
|
|
43
|
+
domain,
|
|
44
|
+
req,
|
|
45
|
+
null
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return res.status(404).send('Page not found');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports.ResponseManager = ResponseManager;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - TemplateCache
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
8
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
9
|
+
|
|
10
|
+
class TemplateCache
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor(props)
|
|
14
|
+
{
|
|
15
|
+
this.templatesPath = sc.get(props, 'templatesPath', '');
|
|
16
|
+
this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.mustache', '.template']);
|
|
17
|
+
this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
|
|
18
|
+
this.partialsCache = {};
|
|
19
|
+
this.domainPartialsCache = new Map();
|
|
20
|
+
this.domainTemplatesMap = new Map();
|
|
21
|
+
this.templateResolver = sc.get(props, 'templateResolver', false);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async loadPartials()
|
|
25
|
+
{
|
|
26
|
+
let partialsPath = FileHandler.joinPaths(this.templatesPath, 'partials');
|
|
27
|
+
FileHandler.createFolder(partialsPath);
|
|
28
|
+
let partialFiles = FileHandler.getFilesInFolder(partialsPath, this.templateExtensions);
|
|
29
|
+
for(let file of partialFiles){
|
|
30
|
+
let partialName = this.templateResolver.extractTemplateName(file);
|
|
31
|
+
if(!partialName){
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let partialPath = FileHandler.joinPaths(partialsPath, file);
|
|
35
|
+
let partialContent = FileHandler.readFile(partialPath);
|
|
36
|
+
if(!partialContent){
|
|
37
|
+
Logger.error('Failed to read partial: '+partialPath);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
this.partialsCache[partialName] = partialContent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async loadDomainPartials(domain, domainPath)
|
|
45
|
+
{
|
|
46
|
+
let domainPartialsPath = FileHandler.joinPaths(domainPath, 'partials');
|
|
47
|
+
if(!FileHandler.exists(domainPartialsPath)){
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let domainPartials = {};
|
|
51
|
+
let partialFiles = FileHandler.getFilesInFolder(domainPartialsPath, this.templateExtensions);
|
|
52
|
+
for(let file of partialFiles){
|
|
53
|
+
let partialName = this.templateResolver.extractTemplateName(file);
|
|
54
|
+
if(!partialName){
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let partialPath = FileHandler.joinPaths(domainPartialsPath, file);
|
|
58
|
+
let partialContent = FileHandler.readFile(partialPath);
|
|
59
|
+
if(!partialContent){
|
|
60
|
+
Logger.error('Failed to read domain partial: '+partialPath);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
domainPartials[partialName] = partialContent;
|
|
64
|
+
}
|
|
65
|
+
this.domainPartialsCache.set(domain, domainPartials);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async setupDomainTemplates()
|
|
69
|
+
{
|
|
70
|
+
let domainsPath = FileHandler.joinPaths(this.templatesPath, 'domains');
|
|
71
|
+
if(!FileHandler.exists(domainsPath)){
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let domainFolders = FileHandler.fetchSubFoldersList(domainsPath);
|
|
75
|
+
for(let domain of domainFolders){
|
|
76
|
+
let domainPath = FileHandler.joinPaths(domainsPath, domain);
|
|
77
|
+
this.domainTemplatesMap.set(domain, domainPath);
|
|
78
|
+
await this.loadDomainPartials(domain, domainPath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getPartialsForDomain(domain)
|
|
83
|
+
{
|
|
84
|
+
let resolvedDomain = this.templateResolver.resolveDomainToFolder(domain);
|
|
85
|
+
let domainPartials = this.domainPartialsCache.get(resolvedDomain);
|
|
86
|
+
if(!domainPartials && this.defaultDomain && resolvedDomain !== this.defaultDomain){
|
|
87
|
+
domainPartials = this.domainPartialsCache.get(this.defaultDomain);
|
|
88
|
+
}
|
|
89
|
+
if(!domainPartials){
|
|
90
|
+
return this.partialsCache;
|
|
91
|
+
}
|
|
92
|
+
return Object.assign({}, this.partialsCache, domainPartials);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getDomainTemplatesMap()
|
|
96
|
+
{
|
|
97
|
+
return this.domainTemplatesMap;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports.TemplateCache = TemplateCache;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Frontend - TemplateResolver
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
8
|
+
const { sc } = require('@reldens/utils');
|
|
9
|
+
|
|
10
|
+
class TemplateResolver
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor(props)
|
|
14
|
+
{
|
|
15
|
+
this.templatesPath = sc.get(props, 'templatesPath', '');
|
|
16
|
+
this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.mustache', '.template']);
|
|
17
|
+
this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
|
|
18
|
+
this.domainMapping = sc.get(props, 'domainMapping', {});
|
|
19
|
+
this.siteKeyMapping = sc.get(props, 'siteKeyMapping', {});
|
|
20
|
+
this.domainTemplatesMap = sc.get(props, 'domainTemplatesMap', new Map());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extractTemplateName(filename)
|
|
24
|
+
{
|
|
25
|
+
for(let extension of this.templateExtensions){
|
|
26
|
+
if(filename.endsWith(extension)){
|
|
27
|
+
return filename.replace(extension, '');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resolveDomainToFolder(domain)
|
|
34
|
+
{
|
|
35
|
+
if(!domain){
|
|
36
|
+
domain = this.defaultDomain;
|
|
37
|
+
}
|
|
38
|
+
return sc.get(this.domainMapping, domain, domain);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resolveDomainToSiteKey(domain)
|
|
42
|
+
{
|
|
43
|
+
return sc.get(this.siteKeyMapping, this.resolveDomainToFolder(domain), 'default');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
findTemplatePath(templateName, domain)
|
|
47
|
+
{
|
|
48
|
+
let resolvedDomain = this.resolveDomainToFolder(domain);
|
|
49
|
+
if(resolvedDomain){
|
|
50
|
+
let domainPath = this.domainTemplatesMap.get(resolvedDomain);
|
|
51
|
+
if(domainPath){
|
|
52
|
+
let domainTemplatePath = this.findTemplateInPath(templateName, domainPath);
|
|
53
|
+
if(domainTemplatePath){
|
|
54
|
+
return domainTemplatePath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if(this.defaultDomain && resolvedDomain !== this.defaultDomain){
|
|
58
|
+
let defaultDomainPath = this.domainTemplatesMap.get(this.defaultDomain);
|
|
59
|
+
if(defaultDomainPath){
|
|
60
|
+
let defaultTemplatePath = this.findTemplateInPath(templateName, defaultDomainPath);
|
|
61
|
+
if(defaultTemplatePath){
|
|
62
|
+
return defaultTemplatePath;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return this.findTemplateInPath(templateName, this.templatesPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
findTemplateInPath(templateName, basePath)
|
|
71
|
+
{
|
|
72
|
+
for(let extension of this.templateExtensions){
|
|
73
|
+
let templatePath = FileHandler.joinPaths(basePath, templateName + extension);
|
|
74
|
+
if(FileHandler.exists(templatePath)){
|
|
75
|
+
return templatePath;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
findLayoutPath(layoutName, domain)
|
|
82
|
+
{
|
|
83
|
+
let resolvedDomain = this.resolveDomainToFolder(domain);
|
|
84
|
+
if(resolvedDomain){
|
|
85
|
+
let domainPath = this.domainTemplatesMap.get(resolvedDomain);
|
|
86
|
+
if(domainPath){
|
|
87
|
+
let domainLayoutPath = this.findTemplateInPath('layouts/' + layoutName, domainPath);
|
|
88
|
+
if(domainLayoutPath){
|
|
89
|
+
return domainLayoutPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return this.findTemplateInPath('layouts/' + layoutName, this.templatesPath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
findTemplateByPath(path, domain)
|
|
97
|
+
{
|
|
98
|
+
if('/' === path){
|
|
99
|
+
path = '/index';
|
|
100
|
+
}
|
|
101
|
+
let templatePath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
102
|
+
templatePath = templatePath.startsWith('/') ? templatePath.substring(1) : templatePath;
|
|
103
|
+
if('page' === templatePath){
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return this.findTemplatePath(templatePath, domain);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports.TemplateResolver = TemplateResolver;
|