@reldens/cms 0.18.0 → 0.20.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 +260 -20
- package/admin/reldens-admin-client.css +127 -80
- package/admin/reldens-admin-client.js +24 -0
- package/admin/templates/clear-all-cache-button.html +18 -0
- package/lib/admin-manager/contents-builder.js +7 -6
- package/lib/admin-manager/router-contents.js +58 -16
- package/lib/admin-manager-validator.js +2 -1
- package/lib/admin-manager.js +4 -2
- package/lib/admin-translations.js +9 -1
- package/lib/cache/add-cache-button-subscriber.js +53 -5
- package/lib/cache/cache-manager.js +47 -8
- package/lib/cache/cache-routes-handler.js +23 -0
- package/lib/cms-pages-route-manager.js +16 -4
- package/lib/frontend.js +310 -119
- package/lib/manager.js +43 -3
- package/lib/pagination-handler.js +243 -0
- package/lib/search-renderer.js +116 -0
- package/lib/search.js +344 -0
- package/lib/template-engine/asset-transformer.js +41 -0
- package/lib/template-engine/collections-single-transformer.js +70 -0
- package/lib/template-engine/collections-transformer-base.js +84 -0
- package/lib/template-engine/collections-transformer.js +374 -0
- package/lib/template-engine/date-transformer.js +53 -0
- package/lib/template-engine/entities-transformer.js +67 -0
- package/lib/template-engine/partials-transformer.js +175 -0
- package/lib/template-engine/system-variables-provider.js +105 -0
- package/lib/template-engine/translate-transformer.js +98 -0
- package/lib/template-engine/translation-service.js +104 -0
- package/lib/template-engine/url-transformer.js +41 -0
- package/lib/template-engine.js +133 -438
- package/lib/templates-list.js +1 -0
- package/migrations/install.sql +18 -18
- package/package.json +4 -4
- package/templates/page.html +19 -2
- package/templates/partials/entriesListView.html +14 -0
- package/templates/partials/pagedCollection.html +33 -0
package/lib/template-engine.js
CHANGED
|
@@ -4,8 +4,17 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const { Logger, sc } = require('@reldens/utils');
|
|
8
7
|
const { JsonFieldsParser } = require('./json-fields-parser');
|
|
8
|
+
const { EntitiesTransformer } = require('./template-engine/entities-transformer');
|
|
9
|
+
const { CollectionsTransformer } = require('./template-engine/collections-transformer');
|
|
10
|
+
const { CollectionsSingleTransformer } = require('./template-engine/collections-single-transformer');
|
|
11
|
+
const { PartialsTransformer } = require('./template-engine/partials-transformer');
|
|
12
|
+
const { UrlTransformer } = require('./template-engine/url-transformer');
|
|
13
|
+
const { AssetTransformer } = require('./template-engine/asset-transformer');
|
|
14
|
+
const { DateTransformer } = require('./template-engine/date-transformer');
|
|
15
|
+
const { TranslateTransformer } = require('./template-engine/translate-transformer');
|
|
16
|
+
const { SystemVariablesProvider } = require('./template-engine/system-variables-provider');
|
|
17
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
9
18
|
|
|
10
19
|
class TemplateEngine
|
|
11
20
|
{
|
|
@@ -15,45 +24,144 @@ class TemplateEngine
|
|
|
15
24
|
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
16
25
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
17
26
|
this.getPartials = sc.get(props, 'getPartials', false);
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
|
|
27
|
+
this.events = sc.get(props, 'events', false);
|
|
28
|
+
this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
|
|
29
|
+
this.projectRoot = sc.get(props, 'projectRoot', './');
|
|
30
|
+
this.publicPath = sc.get(props, 'publicPath', './public');
|
|
31
|
+
this.jsonFieldsParser = new JsonFieldsParser({entitiesConfig: sc.get(props, 'entitiesConfig', {})});
|
|
32
|
+
this.systemVariablesProvider = new SystemVariablesProvider({
|
|
33
|
+
defaultDomain: this.defaultDomain,
|
|
34
|
+
projectRoot: this.projectRoot,
|
|
35
|
+
publicPath: this.publicPath
|
|
21
36
|
});
|
|
37
|
+
this.entitiesTransformer = new EntitiesTransformer({
|
|
38
|
+
dataServer: this.dataServer,
|
|
39
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
40
|
+
processAllTemplateFunctions: this.processAllTemplateFunctions.bind(this)
|
|
41
|
+
});
|
|
42
|
+
this.collectionsSingleTransformer = new CollectionsSingleTransformer({
|
|
43
|
+
dataServer: this.dataServer,
|
|
44
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
45
|
+
renderEngine: this.renderEngine,
|
|
46
|
+
getPartials: this.getPartials,
|
|
47
|
+
findAllPartialTags: this.findAllPartialTags.bind(this),
|
|
48
|
+
loadPartialTemplate: this.loadPartialTemplate.bind(this),
|
|
49
|
+
processAllTemplateFunctions: this.processAllTemplateFunctions.bind(this)
|
|
50
|
+
});
|
|
51
|
+
this.collectionsTransformer = new CollectionsTransformer({
|
|
52
|
+
dataServer: this.dataServer,
|
|
53
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
54
|
+
renderEngine: this.renderEngine,
|
|
55
|
+
getPartials: this.getPartials,
|
|
56
|
+
findAllPartialTags: this.findAllPartialTags.bind(this),
|
|
57
|
+
loadPartialTemplate: this.loadPartialTemplate.bind(this),
|
|
58
|
+
processAllTemplateFunctions: this.processAllTemplateFunctions.bind(this)
|
|
59
|
+
});
|
|
60
|
+
this.partialsTransformer = new PartialsTransformer({
|
|
61
|
+
renderEngine: this.renderEngine,
|
|
62
|
+
getPartials: this.getPartials
|
|
63
|
+
});
|
|
64
|
+
this.urlTransformer = new UrlTransformer();
|
|
65
|
+
this.assetTransformer = new AssetTransformer();
|
|
66
|
+
this.dateTransformer = new DateTransformer({
|
|
67
|
+
defaultFormat: sc.get(props, 'defaultDateFormat', 'Y-m-d H:i:s')
|
|
68
|
+
});
|
|
69
|
+
this.translateTransformer = new TranslateTransformer({
|
|
70
|
+
projectRoot: this.projectRoot,
|
|
71
|
+
defaultLocale: sc.get(props, 'defaultLocale', 'en'),
|
|
72
|
+
fallbackLocale: sc.get(props, 'fallbackLocale', 'en')
|
|
73
|
+
});
|
|
74
|
+
this.transformers = [
|
|
75
|
+
this.entitiesTransformer,
|
|
76
|
+
this.collectionsSingleTransformer,
|
|
77
|
+
this.collectionsTransformer,
|
|
78
|
+
this.partialsTransformer,
|
|
79
|
+
this.urlTransformer,
|
|
80
|
+
this.assetTransformer,
|
|
81
|
+
this.dateTransformer,
|
|
82
|
+
this.translateTransformer
|
|
83
|
+
];
|
|
22
84
|
}
|
|
23
85
|
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
this.currentDomain = domain;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async processAllTemplateFunctions(template)
|
|
86
|
+
async processAllTemplateFunctions(template, domain, req, systemVariables)
|
|
30
87
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
88
|
+
let processedTemplate = template;
|
|
89
|
+
for(let transformer of this.transformers){
|
|
90
|
+
if(sc.isFunction(transformer.transform)){
|
|
91
|
+
processedTemplate = await transformer.transform(processedTemplate, domain, req, systemVariables);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return processedTemplate;
|
|
38
95
|
}
|
|
39
96
|
|
|
40
|
-
async render(template, data, partials)
|
|
97
|
+
async render(template, data, partials, domain, req, route, currentEntityData)
|
|
41
98
|
{
|
|
42
99
|
if(!this.renderEngine){
|
|
43
|
-
Logger.
|
|
100
|
+
Logger.critical('Render engine not provided');
|
|
44
101
|
return '';
|
|
45
102
|
}
|
|
46
103
|
if(!sc.isFunction(this.renderEngine.render)){
|
|
47
|
-
Logger.
|
|
104
|
+
Logger.critical('Render engine does not contain a render method');
|
|
48
105
|
return '';
|
|
49
106
|
}
|
|
50
|
-
|
|
51
|
-
|
|
107
|
+
if(!this.events){
|
|
108
|
+
Logger.critical('Events manager not provided');
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
let systemVariables = this.systemVariablesProvider.buildSystemVariables(req, route, domain);
|
|
112
|
+
let renderContext = {
|
|
113
|
+
template,
|
|
52
114
|
data,
|
|
115
|
+
partials,
|
|
116
|
+
domain,
|
|
117
|
+
req,
|
|
118
|
+
route,
|
|
119
|
+
currentEntityData
|
|
120
|
+
};
|
|
121
|
+
let eventData = {
|
|
122
|
+
variables: systemVariables,
|
|
123
|
+
renderContext
|
|
124
|
+
};
|
|
125
|
+
await this.events.emit('reldens.afterVariablesCreated', eventData);
|
|
126
|
+
let enhancedData = this.buildEnhancedRenderData(data, eventData.variables, currentEntityData);
|
|
127
|
+
let beforeProcessData = {
|
|
128
|
+
content: template,
|
|
129
|
+
variables: enhancedData,
|
|
130
|
+
renderContext
|
|
131
|
+
};
|
|
132
|
+
await this.events.emit('reldens.beforeContentProcess', beforeProcessData);
|
|
133
|
+
let processedTemplate = await this.processAllTemplateFunctions(
|
|
134
|
+
beforeProcessData.content,
|
|
135
|
+
domain,
|
|
136
|
+
req,
|
|
137
|
+
eventData.variables
|
|
138
|
+
);
|
|
139
|
+
let afterProcessData = {
|
|
140
|
+
processedContent: processedTemplate,
|
|
141
|
+
variables: enhancedData,
|
|
142
|
+
renderContext
|
|
143
|
+
};
|
|
144
|
+
await this.events.emit('reldens.afterContentProcess', afterProcessData);
|
|
145
|
+
return this.renderEngine.render(
|
|
146
|
+
this.unescapeHtml(afterProcessData.processedContent),
|
|
147
|
+
enhancedData,
|
|
53
148
|
partials
|
|
54
149
|
);
|
|
55
150
|
}
|
|
56
151
|
|
|
152
|
+
buildEnhancedRenderData(originalData, systemVariables, currentEntityData)
|
|
153
|
+
{
|
|
154
|
+
let enhancedData = Object.assign({}, originalData);
|
|
155
|
+
enhancedData.currentRequest = systemVariables.currentRequest;
|
|
156
|
+
enhancedData.currentRoute = systemVariables.currentRoute;
|
|
157
|
+
enhancedData.currentDomain = systemVariables.currentDomain;
|
|
158
|
+
enhancedData.systemInfo = systemVariables.systemInfo;
|
|
159
|
+
if(currentEntityData){
|
|
160
|
+
enhancedData.currentEntity = currentEntityData;
|
|
161
|
+
}
|
|
162
|
+
return enhancedData;
|
|
163
|
+
}
|
|
164
|
+
|
|
57
165
|
unescapeHtml(text)
|
|
58
166
|
{
|
|
59
167
|
return text
|
|
@@ -67,427 +175,14 @@ class TemplateEngine
|
|
|
67
175
|
.replace(/&/g, '&');
|
|
68
176
|
}
|
|
69
177
|
|
|
70
|
-
getEntityRegex()
|
|
71
|
-
{
|
|
72
|
-
return /<entity\s+name="([^"]+)"(?:\s+field="([^"]+)"\s+value="([^"]+)"|\s+id="([^"]+)")?\s*\/?>/g;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getSingleFieldCollectionRegex()
|
|
76
|
-
{
|
|
77
|
-
return /<collection\s+([^>]+)\/>/g;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getLoopCollectionStartRegex()
|
|
81
|
-
{
|
|
82
|
-
return /<collection\s+([^>]+)>/g;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getLoopCollectionEndRegex()
|
|
86
|
-
{
|
|
87
|
-
return new RegExp('<\\/collection>');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
extractAttributeValue(tagContent, attributeName)
|
|
91
|
-
{
|
|
92
|
-
let regex = new RegExp(attributeName + '=([\'"])([^\'"]*)\\1');
|
|
93
|
-
let match = tagContent.match(regex);
|
|
94
|
-
return match ? match[2] : '';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async processEntityFunctions(template)
|
|
98
|
-
{
|
|
99
|
-
let processedTemplate = template;
|
|
100
|
-
for(let match of template.matchAll(this.getEntityRegex())){
|
|
101
|
-
let tableName = match[1];
|
|
102
|
-
let field = sc.get(match, '2', 'id');
|
|
103
|
-
let value = sc.get(match, '3', sc.get(match, '4', ''));
|
|
104
|
-
if(!value){
|
|
105
|
-
Logger.warning('Entity tag missing value: '+match[0]);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
processedTemplate = processedTemplate.replace(
|
|
109
|
-
match[0],
|
|
110
|
-
await this.processAllTemplateFunctions(
|
|
111
|
-
sc.get(await this.fetchEntityForTemplate(tableName, value, field), 'content', '')
|
|
112
|
-
)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
return processedTemplate;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async processSingleFieldCollections(template)
|
|
119
|
-
{
|
|
120
|
-
let processedTemplate = template;
|
|
121
|
-
for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
|
|
122
|
-
let tagContent = match[1];
|
|
123
|
-
if(!tagContent.includes('field=')){
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
let tableName = this.extractAttributeValue(tagContent, 'name');
|
|
127
|
-
let filtersJson = this.extractAttributeValue(tagContent, 'filters');
|
|
128
|
-
let queryOptionsJson = this.extractAttributeValue(tagContent, 'data');
|
|
129
|
-
let relationsString = this.extractAttributeValue(tagContent, 'relations');
|
|
130
|
-
let fieldName = this.extractAttributeValue(tagContent, 'field');
|
|
131
|
-
processedTemplate = processedTemplate.replace(
|
|
132
|
-
match[0],
|
|
133
|
-
this.extractFieldValues(
|
|
134
|
-
await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson, relationsString),
|
|
135
|
-
fieldName
|
|
136
|
-
)
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
return processedTemplate;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async processLoopCollections(template)
|
|
143
|
-
{
|
|
144
|
-
let processedTemplate = template;
|
|
145
|
-
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
146
|
-
for(let i = matches.length - 1; i >= 0; i--){
|
|
147
|
-
let startMatch = matches[i];
|
|
148
|
-
let tagContent = startMatch[1];
|
|
149
|
-
if(tagContent.includes('field=')){
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
let tableName = this.extractAttributeValue(tagContent, 'name');
|
|
153
|
-
let filtersJson = this.extractAttributeValue(tagContent, 'filters');
|
|
154
|
-
let queryOptionsJson = this.extractAttributeValue(tagContent, 'data');
|
|
155
|
-
let relationsString = this.extractAttributeValue(tagContent, 'relations');
|
|
156
|
-
let loopResult = await this.processLoopCollection(
|
|
157
|
-
processedTemplate,
|
|
158
|
-
startMatch,
|
|
159
|
-
tableName,
|
|
160
|
-
filtersJson,
|
|
161
|
-
relationsString,
|
|
162
|
-
queryOptionsJson
|
|
163
|
-
);
|
|
164
|
-
if(loopResult){
|
|
165
|
-
processedTemplate = loopResult;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return processedTemplate;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async processCustomPartials(template)
|
|
172
|
-
{
|
|
173
|
-
let processedTemplate = template;
|
|
174
|
-
let partialTags = this.findAllPartialTags(template);
|
|
175
|
-
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
176
|
-
let tag = partialTags[i];
|
|
177
|
-
let partialContent = this.loadPartialTemplate(tag.name);
|
|
178
|
-
if(!partialContent){
|
|
179
|
-
Logger.warning('Partial template not found: ' + tag.name);
|
|
180
|
-
processedTemplate = processedTemplate.substring(0, tag.start)+''+processedTemplate.substring(tag.end);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
184
|
-
let renderData = { vars: tag.attributes };
|
|
185
|
-
let partials = {[tag.name]: partialContent};
|
|
186
|
-
processedTemplate = processedTemplate.substring(0, tag.start) +
|
|
187
|
-
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
188
|
-
processedTemplate.substring(tag.end);
|
|
189
|
-
}
|
|
190
|
-
return processedTemplate;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
178
|
findAllPartialTags(template)
|
|
194
179
|
{
|
|
195
|
-
|
|
196
|
-
let pos = 0;
|
|
197
|
-
let partial = '<partial';
|
|
198
|
-
for(let tagStart = template.indexOf(partial, pos); -1 !== tagStart; tagStart=template.indexOf(partial, pos)){
|
|
199
|
-
let tagEnd = this.findPartialTagEnd(template, tagStart);
|
|
200
|
-
if(-1 === tagEnd){
|
|
201
|
-
pos = tagStart + partial.length;
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
let fullTag = template.substring(tagStart, tagEnd);
|
|
205
|
-
let nameMatch = fullTag.match(/name=["']([^"']+)["']/);
|
|
206
|
-
if(!nameMatch){
|
|
207
|
-
pos = tagStart + partial.length;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
let partialName = nameMatch[1];
|
|
211
|
-
let attributes = this.parsePartialAttributes(fullTag, partialName);
|
|
212
|
-
partialTags.push({
|
|
213
|
-
start: tagStart,
|
|
214
|
-
end: tagEnd,
|
|
215
|
-
name: partialName,
|
|
216
|
-
attributes: attributes,
|
|
217
|
-
fullTag: fullTag
|
|
218
|
-
});
|
|
219
|
-
pos = tagEnd;
|
|
220
|
-
}
|
|
221
|
-
return partialTags;
|
|
180
|
+
return this.partialsTransformer.findAllPartialTags(template);
|
|
222
181
|
}
|
|
223
182
|
|
|
224
|
-
|
|
183
|
+
loadPartialTemplate(partialName, domain)
|
|
225
184
|
{
|
|
226
|
-
|
|
227
|
-
let quoteChar = '';
|
|
228
|
-
let selfCloseTag = '/>';
|
|
229
|
-
let openCloseTag = '</partial>';
|
|
230
|
-
for(let i = tagStart; i < template.length; i++){
|
|
231
|
-
let char = template[i];
|
|
232
|
-
if(!inQuotes && ('"' === char || "'" === char)){
|
|
233
|
-
inQuotes = true;
|
|
234
|
-
quoteChar = char;
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if(inQuotes && char === quoteChar && '\\' !== template[i - 1]){
|
|
238
|
-
inQuotes = false;
|
|
239
|
-
quoteChar = '';
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if(!inQuotes){
|
|
243
|
-
if(template.substring(i, i + selfCloseTag.length) === selfCloseTag){
|
|
244
|
-
return i + selfCloseTag.length;
|
|
245
|
-
}
|
|
246
|
-
if('>' === char){
|
|
247
|
-
let closeIndex = template.indexOf(openCloseTag, i);
|
|
248
|
-
if(-1 !== closeIndex){
|
|
249
|
-
return closeIndex + openCloseTag.length;
|
|
250
|
-
}
|
|
251
|
-
return i + 1;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return -1;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
parsePartialAttributes(fullTag, partialName)
|
|
259
|
-
{
|
|
260
|
-
let namePattern = 'name=' + this.getQuotePattern(fullTag, partialName);
|
|
261
|
-
let nameIndex = fullTag.indexOf(namePattern);
|
|
262
|
-
if(-1 === nameIndex){
|
|
263
|
-
return {};
|
|
264
|
-
}
|
|
265
|
-
let attributesStart = nameIndex + namePattern.length;
|
|
266
|
-
let attributesEnd = fullTag.lastIndexOf('/>');
|
|
267
|
-
if(-1 === attributesEnd){
|
|
268
|
-
attributesEnd = fullTag.lastIndexOf('</partial>');
|
|
269
|
-
}
|
|
270
|
-
if(-1 === attributesEnd){
|
|
271
|
-
attributesEnd = fullTag.lastIndexOf('>');
|
|
272
|
-
}
|
|
273
|
-
if(-1 === attributesEnd || attributesEnd <= attributesStart){
|
|
274
|
-
return {};
|
|
275
|
-
}
|
|
276
|
-
let attributesString = fullTag.substring(attributesStart, attributesEnd).trim();
|
|
277
|
-
return this.extractAttributesObject(attributesString);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
getQuotePattern(fullTag, partialName)
|
|
281
|
-
{
|
|
282
|
-
if(fullTag.includes('name="' + partialName + '"')){
|
|
283
|
-
return '"' + partialName + '"';
|
|
284
|
-
}
|
|
285
|
-
if(fullTag.includes("name='" + partialName + "'")){
|
|
286
|
-
return "'" + partialName + "'";
|
|
287
|
-
}
|
|
288
|
-
return '"' + partialName + '"';
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
extractAttributesObject(attributesString)
|
|
292
|
-
{
|
|
293
|
-
if(!attributesString){
|
|
294
|
-
return {};
|
|
295
|
-
}
|
|
296
|
-
let attributes = {};
|
|
297
|
-
let valueRegex = /(\w+)=(['"])((?:(?!\2)[^\\]|\\.)*)(\2)/g;
|
|
298
|
-
for(let match of attributesString.matchAll(valueRegex)){
|
|
299
|
-
attributes[match[1]] = match[3];
|
|
300
|
-
}
|
|
301
|
-
let booleanRegex = /\b(\w+)(?!\s*=)/g;
|
|
302
|
-
for(let match of attributesString.matchAll(booleanRegex)){
|
|
303
|
-
if(!sc.hasOwn(attributes, match[1])){
|
|
304
|
-
attributes[match[1]] = true;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return attributes;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
loadPartialTemplate(partialName)
|
|
311
|
-
{
|
|
312
|
-
if(!this.getPartials){
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
let partials = this.getPartials(this.currentDomain);
|
|
316
|
-
if(sc.hasOwn(partials, partialName)){
|
|
317
|
-
return partials[partialName];
|
|
318
|
-
}
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async processLoopCollection(template, startMatch, tableName, filtersJson, relationsString, queryOptionsJson)
|
|
323
|
-
{
|
|
324
|
-
let startPos = startMatch.index;
|
|
325
|
-
let startEnd = startPos + startMatch[0].length;
|
|
326
|
-
let endMatch = this.getLoopCollectionEndRegex(tableName).exec(template.substring(startEnd));
|
|
327
|
-
if(!endMatch){
|
|
328
|
-
Logger.warning('No matching end tag found for collection: '+tableName);
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
let endPos = startEnd + endMatch.index;
|
|
332
|
-
let loopContent = template.substring(startEnd, endPos);
|
|
333
|
-
let collectionData = await this.fetchCollectionForTemplate(
|
|
334
|
-
tableName,
|
|
335
|
-
filtersJson,
|
|
336
|
-
queryOptionsJson,
|
|
337
|
-
relationsString
|
|
338
|
-
);
|
|
339
|
-
let renderedContent = await this.renderCollectionLoop(loopContent, collectionData);
|
|
340
|
-
return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async renderCollectionLoop(loopContent, collectionData)
|
|
344
|
-
{
|
|
345
|
-
let renderedContent = '';
|
|
346
|
-
for(let row of collectionData){
|
|
347
|
-
renderedContent += await this.processPartialsInLoop(loopContent, row);
|
|
348
|
-
}
|
|
349
|
-
return renderedContent;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async processPartialsInLoop(content, rowData)
|
|
353
|
-
{
|
|
354
|
-
let processedContent = content;
|
|
355
|
-
let partialTags = this.findAllPartialTags(content);
|
|
356
|
-
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
357
|
-
let tag = partialTags[i];
|
|
358
|
-
let partialContent = this.loadPartialTemplate(tag.name);
|
|
359
|
-
if(!partialContent){
|
|
360
|
-
Logger.warning('Partial template not found: ' + tag.name);
|
|
361
|
-
processedContent = processedContent.substring(0, tag.start)+''+processedContent.substring(tag.end);
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if(sc.hasOwn(tag.attributes, 'row')){
|
|
365
|
-
processedContent = processedContent.substring(0, tag.start)
|
|
366
|
-
+this.renderEngine.render(partialContent, {row: rowData}, this.getPartials(this.currentDomain))
|
|
367
|
-
+processedContent.substring(tag.end);
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
371
|
-
let renderData = { vars: tag.attributes };
|
|
372
|
-
let partials = {[tag.name]: partialContent};
|
|
373
|
-
processedContent = processedContent.substring(0, tag.start) +
|
|
374
|
-
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
375
|
-
processedContent.substring(tag.end);
|
|
376
|
-
}
|
|
377
|
-
return this.renderEngine.render(processedContent, {row: rowData}, this.getPartials(this.currentDomain));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
extractFieldValues(collectionData, fieldName)
|
|
381
|
-
{
|
|
382
|
-
let fieldValues = '';
|
|
383
|
-
for(let row of collectionData){
|
|
384
|
-
fieldValues += sc.get(row, fieldName, '');
|
|
385
|
-
}
|
|
386
|
-
return fieldValues;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async fetchEntityForTemplate(tableName, identifier, identifierField)
|
|
390
|
-
{
|
|
391
|
-
let entity = this.dataServer.getEntity(tableName);
|
|
392
|
-
if(!entity){
|
|
393
|
-
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
396
|
-
let result = await entity.loadOneBy(identifierField, identifier);
|
|
397
|
-
if(!result){
|
|
398
|
-
return false;
|
|
399
|
-
}
|
|
400
|
-
return this.jsonFieldsParser.parseJsonFields(
|
|
401
|
-
result,
|
|
402
|
-
this.jsonFieldsParser.getJsonFieldsForEntity(tableName)
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
convertJsObjectToJson(jsObjectString)
|
|
407
|
-
{
|
|
408
|
-
if(!jsObjectString || '' === jsObjectString.trim()){
|
|
409
|
-
return '';
|
|
410
|
-
}
|
|
411
|
-
return jsObjectString.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
parseRelationsString(relationsString)
|
|
415
|
-
{
|
|
416
|
-
if(!relationsString || '' === relationsString.trim()){
|
|
417
|
-
return [];
|
|
418
|
-
}
|
|
419
|
-
return relationsString.split(',').map(relation => relation.trim()).filter(relation => '' !== relation);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson, relationsString)
|
|
423
|
-
{
|
|
424
|
-
let entity = this.dataServer.getEntity(tableName);
|
|
425
|
-
if(!entity){
|
|
426
|
-
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
427
|
-
return [];
|
|
428
|
-
}
|
|
429
|
-
let filters = false;
|
|
430
|
-
if(filtersJson && '' !== filtersJson.trim()){
|
|
431
|
-
let convertedFiltersJson = this.convertJsObjectToJson(filtersJson);
|
|
432
|
-
filters = sc.parseJson(convertedFiltersJson, false);
|
|
433
|
-
if(!filters){
|
|
434
|
-
Logger.warning('Invalid filters JSON: '+filtersJson);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
let originalState = this.preserveEntityState(entity);
|
|
438
|
-
let queryOptions = {};
|
|
439
|
-
if(queryOptionsJson && '' !== queryOptionsJson.trim()){
|
|
440
|
-
let convertedOptionsJson = this.convertJsObjectToJson(queryOptionsJson);
|
|
441
|
-
queryOptions = sc.parseJson(convertedOptionsJson, {});
|
|
442
|
-
if(!queryOptions){
|
|
443
|
-
Logger.warning('Invalid query options JSON: '+queryOptionsJson);
|
|
444
|
-
queryOptions = {};
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
this.applyQueryOptions(entity, queryOptions);
|
|
448
|
-
let relations = this.parseRelationsString(relationsString);
|
|
449
|
-
let result = 0 < relations.length
|
|
450
|
-
? await entity.loadWithRelations(filters || {}, relations)
|
|
451
|
-
: filters ? await entity.load(filters) : await entity.loadAll();
|
|
452
|
-
this.restoreEntityState(entity, originalState);
|
|
453
|
-
return this.jsonFieldsParser.parseJsonFields(
|
|
454
|
-
result,
|
|
455
|
-
this.jsonFieldsParser.getJsonFieldsForEntity(tableName)
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
preserveEntityState(entity)
|
|
460
|
-
{
|
|
461
|
-
return {
|
|
462
|
-
limit: entity.limit,
|
|
463
|
-
offset: entity.offset,
|
|
464
|
-
sortBy: entity.sortBy,
|
|
465
|
-
sortDirection: entity.sortDirection
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
restoreEntityState(entity, originalState)
|
|
470
|
-
{
|
|
471
|
-
entity.limit = originalState.limit;
|
|
472
|
-
entity.offset = originalState.offset;
|
|
473
|
-
entity.sortBy = originalState.sortBy;
|
|
474
|
-
entity.sortDirection = originalState.sortDirection;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
applyQueryOptions(entity, queryOptions)
|
|
478
|
-
{
|
|
479
|
-
if(sc.hasOwn(queryOptions, 'limit')){
|
|
480
|
-
entity.limit = queryOptions.limit;
|
|
481
|
-
}
|
|
482
|
-
if(sc.hasOwn(queryOptions, 'offset')){
|
|
483
|
-
entity.offset = queryOptions.offset;
|
|
484
|
-
}
|
|
485
|
-
if(sc.hasOwn(queryOptions, 'sortBy')){
|
|
486
|
-
entity.sortBy = queryOptions.sortBy;
|
|
487
|
-
}
|
|
488
|
-
if(sc.hasOwn(queryOptions, 'sortDirection')){
|
|
489
|
-
entity.sortDirection = queryOptions.sortDirection;
|
|
490
|
-
}
|
|
185
|
+
return this.partialsTransformer.loadPartialTemplate(partialName, domain);
|
|
491
186
|
}
|
|
492
187
|
|
|
493
188
|
}
|
package/lib/templates-list.js
CHANGED
|
@@ -19,6 +19,7 @@ module.exports.TemplatesList = {
|
|
|
19
19
|
paginationLink: 'pagination-link.html',
|
|
20
20
|
defaultCopyRight: 'default-copyright.html',
|
|
21
21
|
cacheCleanButton: 'cache-clean-button.html',
|
|
22
|
+
clearAllCacheButton: 'clear-all-cache-button.html',
|
|
22
23
|
fields: {
|
|
23
24
|
view: {
|
|
24
25
|
text: 'text.html',
|
package/migrations/install.sql
CHANGED
|
@@ -29,34 +29,34 @@ CREATE TABLE IF NOT EXISTS `cms_categories` (
|
|
|
29
29
|
|
|
30
30
|
CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
31
31
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
32
|
-
`title` VARCHAR(255)
|
|
33
|
-
`content` LONGTEXT
|
|
34
|
-
`json_data` JSON NULL,
|
|
35
|
-
`template` VARCHAR(255)
|
|
36
|
-
`layout` VARCHAR(255)
|
|
32
|
+
`title` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
33
|
+
`content` LONGTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
34
|
+
`json_data` JSON NULL DEFAULT NULL,
|
|
35
|
+
`template` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
36
|
+
`layout` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
37
37
|
`category_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
38
38
|
`route_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
39
39
|
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
40
|
-
`meta_title` VARCHAR(255)
|
|
41
|
-
`meta_description` VARCHAR(255)
|
|
42
|
-
`meta_robots` VARCHAR(255)
|
|
40
|
+
`meta_title` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
41
|
+
`meta_description` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
42
|
+
`meta_robots` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
43
43
|
`meta_theme_color` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
44
|
-
`meta_og_title` VARCHAR(255)
|
|
45
|
-
`meta_og_description` VARCHAR(255)
|
|
46
|
-
`meta_og_image` VARCHAR(255)
|
|
47
|
-
`meta_twitter_card_type` VARCHAR(255)
|
|
48
|
-
`canonical_url` VARCHAR(255)
|
|
49
|
-
`status` VARCHAR(20)
|
|
50
|
-
`locale` VARCHAR(10)
|
|
51
|
-
`publish_date` TIMESTAMP NULL,
|
|
52
|
-
`expire_date` TIMESTAMP NULL,
|
|
44
|
+
`meta_og_title` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
45
|
+
`meta_og_description` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
46
|
+
`meta_og_image` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
47
|
+
`meta_twitter_card_type` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
48
|
+
`canonical_url` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
49
|
+
`status` VARCHAR(20) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
50
|
+
`locale` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
51
|
+
`publish_date` TIMESTAMP NULL DEFAULT (NOW()),
|
|
52
|
+
`expire_date` TIMESTAMP NULL DEFAULT NULL,
|
|
53
53
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
54
54
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
55
55
|
PRIMARY KEY (`id`) USING BTREE,
|
|
56
56
|
INDEX `FK_cms_pages_cms_categories` (`category_id`) USING BTREE,
|
|
57
57
|
INDEX `FK_cms_pages_routes` (`route_id`) USING BTREE,
|
|
58
58
|
CONSTRAINT `FK_cms_pages_cms_categories` FOREIGN KEY (`category_id`) REFERENCES `cms_categories` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION,
|
|
59
|
-
CONSTRAINT `FK_cms_pages_routes` FOREIGN KEY (`route_id`) REFERENCES `routes` (`id`) ON UPDATE CASCADE ON DELETE
|
|
59
|
+
CONSTRAINT `FK_cms_pages_routes` FOREIGN KEY (`route_id`) REFERENCES `routes` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION
|
|
60
60
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
61
61
|
|
|
62
62
|
CREATE TABLE IF NOT EXISTS `cms_blocks` (
|