@reldens/cms 0.16.0 → 0.19.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 +92 -9
- package/admin/reldens-admin-client.css +55 -0
- package/admin/reldens-admin-client.js +24 -0
- package/admin/templates/cache-clean-button.html +4 -0
- package/admin/templates/clear-all-cache-button.html +18 -0
- package/admin/templates/fields/view/textarea.html +1 -1
- package/bin/reldens-cms-generate-entities.js +85 -18
- package/bin/reldens-cms.js +6 -6
- package/lib/admin-manager/contents-builder.js +257 -0
- package/lib/admin-manager/router-contents.js +618 -0
- package/lib/admin-manager/router.js +208 -0
- package/lib/admin-manager-validator.js +2 -1
- package/lib/admin-manager.js +116 -990
- package/lib/admin-translations.js +9 -1
- package/lib/cache/add-cache-button-subscriber.js +149 -0
- package/lib/cache/cache-manager.js +168 -0
- package/lib/cache/cache-routes-handler.js +99 -0
- package/lib/cms-pages-route-manager.js +45 -21
- package/lib/frontend.js +288 -71
- package/lib/installer.js +5 -2
- package/lib/json-fields-parser.js +74 -0
- package/lib/manager.js +49 -4
- package/lib/pagination-handler.js +243 -0
- package/lib/search-renderer.js +116 -0
- package/lib/search.js +344 -0
- package/lib/template-engine/collections-single-transformer.js +53 -0
- package/lib/template-engine/collections-transformer-base.js +84 -0
- package/lib/template-engine/collections-transformer.js +353 -0
- package/lib/template-engine/entities-transformer.js +65 -0
- package/lib/template-engine/partials-transformer.js +171 -0
- package/lib/template-engine.js +53 -387
- package/lib/templates-list.js +2 -0
- package/migrations/default-homepage.sql +6 -6
- package/migrations/install.sql +21 -20
- 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,6 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
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');
|
|
7
12
|
const { Logger, sc } = require('@reldens/utils');
|
|
8
13
|
|
|
9
14
|
class TemplateEngine
|
|
@@ -14,26 +19,52 @@ class TemplateEngine
|
|
|
14
19
|
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
15
20
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
21
|
this.getPartials = sc.get(props, 'getPartials', false);
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
this.jsonFieldsParser = new JsonFieldsParser({entitiesConfig: sc.get(props, 'entitiesConfig', {})});
|
|
23
|
+
this.entitiesTransformer = new EntitiesTransformer({
|
|
24
|
+
dataServer: this.dataServer,
|
|
25
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
26
|
+
processAllTemplateFunctions: this.processAllTemplateFunctions.bind(this)
|
|
27
|
+
});
|
|
28
|
+
this.collectionsSingleTransformer = new CollectionsSingleTransformer({
|
|
29
|
+
dataServer: this.dataServer,
|
|
30
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
31
|
+
renderEngine: this.renderEngine,
|
|
32
|
+
getPartials: this.getPartials,
|
|
33
|
+
findAllPartialTags: this.findAllPartialTags.bind(this),
|
|
34
|
+
loadPartialTemplate: this.loadPartialTemplate.bind(this)
|
|
35
|
+
});
|
|
36
|
+
this.collectionsTransformer = new CollectionsTransformer({
|
|
37
|
+
dataServer: this.dataServer,
|
|
38
|
+
jsonFieldsParser: this.jsonFieldsParser,
|
|
39
|
+
renderEngine: this.renderEngine,
|
|
40
|
+
getPartials: this.getPartials,
|
|
41
|
+
findAllPartialTags: this.findAllPartialTags.bind(this),
|
|
42
|
+
loadPartialTemplate: this.loadPartialTemplate.bind(this)
|
|
43
|
+
});
|
|
44
|
+
this.partialsTransformer = new PartialsTransformer({
|
|
45
|
+
renderEngine: this.renderEngine,
|
|
46
|
+
getPartials: this.getPartials
|
|
47
|
+
});
|
|
48
|
+
this.transformers = [
|
|
49
|
+
this.entitiesTransformer,
|
|
50
|
+
this.collectionsSingleTransformer,
|
|
51
|
+
this.collectionsTransformer,
|
|
52
|
+
this.partialsTransformer
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async processAllTemplateFunctions(template, domain, req)
|
|
26
57
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
let processedTemplate = template;
|
|
59
|
+
for(let transformer of this.transformers){
|
|
60
|
+
if(sc.isFunction(transformer.transform)){
|
|
61
|
+
processedTemplate = await transformer.transform(processedTemplate, domain, req);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return processedTemplate;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
|
-
async render(template, data, partials)
|
|
67
|
+
async render(template, data, partials, domain, req)
|
|
37
68
|
{
|
|
38
69
|
if(!this.renderEngine){
|
|
39
70
|
Logger.error('Render engine not provided');
|
|
@@ -44,7 +75,7 @@ class TemplateEngine
|
|
|
44
75
|
return '';
|
|
45
76
|
}
|
|
46
77
|
return this.renderEngine.render(
|
|
47
|
-
this.unescapeHtml(await this.processAllTemplateFunctions(template)),
|
|
78
|
+
this.unescapeHtml(await this.processAllTemplateFunctions(template, domain, req)),
|
|
48
79
|
data,
|
|
49
80
|
partials
|
|
50
81
|
);
|
|
@@ -63,381 +94,16 @@ class TemplateEngine
|
|
|
63
94
|
.replace(/&/g, '&');
|
|
64
95
|
}
|
|
65
96
|
|
|
66
|
-
getEntityRegex()
|
|
67
|
-
{
|
|
68
|
-
return /<entity\s+name="([^"]+)"(?:\s+field="([^"]+)"\s+value="([^"]+)"|\s+id="([^"]+)")?\s*\/?>/g;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
getSingleFieldCollectionRegex()
|
|
72
|
-
{
|
|
73
|
-
return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?\s+field=(['"])([^'"]+)\5(?:\s+data=(['"])([^'"]*)\7)?\s*\/>/g;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getLoopCollectionStartRegex()
|
|
77
|
-
{
|
|
78
|
-
return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?(?:\s+data=(['"])([^'"]*)\5)?\s*>/g;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
getLoopCollectionEndRegex()
|
|
82
|
-
{
|
|
83
|
-
return new RegExp('<\\/collection>');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async processEntityFunctions(template)
|
|
87
|
-
{
|
|
88
|
-
let processedTemplate = template;
|
|
89
|
-
for(let match of template.matchAll(this.getEntityRegex())){
|
|
90
|
-
let tableName = match[1];
|
|
91
|
-
let field = sc.get(match, '2', 'id');
|
|
92
|
-
let value = sc.get(match, '3', sc.get(match, '4', ''));
|
|
93
|
-
if(!value){
|
|
94
|
-
Logger.warning('Entity tag missing value: '+match[0]);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
processedTemplate = processedTemplate.replace(
|
|
98
|
-
match[0],
|
|
99
|
-
await this.processAllTemplateFunctions(
|
|
100
|
-
sc.get(await this.fetchEntityForTemplate(tableName, value, field), 'content', '')
|
|
101
|
-
)
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return processedTemplate;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async processSingleFieldCollections(template)
|
|
108
|
-
{
|
|
109
|
-
let processedTemplate = template;
|
|
110
|
-
for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
|
|
111
|
-
let tableName = match[2];
|
|
112
|
-
let filtersJson = sc.get(match, '4', '{}');
|
|
113
|
-
let fieldName = match[6];
|
|
114
|
-
let queryOptionsJson = sc.get(match, '8', '{}');
|
|
115
|
-
processedTemplate = processedTemplate.replace(
|
|
116
|
-
match[0],
|
|
117
|
-
this.extractFieldValues(
|
|
118
|
-
await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson),
|
|
119
|
-
fieldName
|
|
120
|
-
)
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return processedTemplate;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async processLoopCollections(template)
|
|
127
|
-
{
|
|
128
|
-
let processedTemplate = template;
|
|
129
|
-
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
130
|
-
for(let i = matches.length - 1; i >= 0; i--){
|
|
131
|
-
let startMatch = matches[i];
|
|
132
|
-
let loopResult = await this.processLoopCollection(
|
|
133
|
-
processedTemplate,
|
|
134
|
-
startMatch,
|
|
135
|
-
startMatch[2],
|
|
136
|
-
sc.get(startMatch, '4', '{}'),
|
|
137
|
-
sc.get(startMatch, '6', '{}')
|
|
138
|
-
);
|
|
139
|
-
if(loopResult){
|
|
140
|
-
processedTemplate = loopResult;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return processedTemplate;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async processCustomPartials(template)
|
|
147
|
-
{
|
|
148
|
-
let processedTemplate = template;
|
|
149
|
-
let partialTags = this.findAllPartialTags(template);
|
|
150
|
-
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
151
|
-
let tag = partialTags[i];
|
|
152
|
-
let partialContent = this.loadPartialTemplate(tag.name);
|
|
153
|
-
if(!partialContent){
|
|
154
|
-
Logger.warning('Partial template not found: ' + tag.name);
|
|
155
|
-
processedTemplate = processedTemplate.substring(0, tag.start) + '' + processedTemplate.substring(tag.end);
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
159
|
-
let renderData = { vars: tag.attributes };
|
|
160
|
-
let partials = {[tag.name]: partialContent};
|
|
161
|
-
processedTemplate = processedTemplate.substring(0, tag.start) +
|
|
162
|
-
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
163
|
-
processedTemplate.substring(tag.end);
|
|
164
|
-
}
|
|
165
|
-
return processedTemplate;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
97
|
findAllPartialTags(template)
|
|
169
98
|
{
|
|
170
|
-
|
|
171
|
-
let searchPos = 0;
|
|
172
|
-
let partialTagName = '<partial';
|
|
173
|
-
for(let tagStart = template.indexOf(partialTagName, searchPos); -1 !== tagStart; tagStart = template.indexOf(partialTagName, searchPos)){
|
|
174
|
-
let tagEnd = this.findPartialTagEnd(template, tagStart);
|
|
175
|
-
if(-1 === tagEnd){
|
|
176
|
-
searchPos = tagStart + partialTagName.length;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
let fullTag = template.substring(tagStart, tagEnd);
|
|
180
|
-
let nameMatch = fullTag.match(/name=["']([^"']+)["']/);
|
|
181
|
-
if(!nameMatch){
|
|
182
|
-
searchPos = tagStart + partialTagName.length;
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
let partialName = nameMatch[1];
|
|
186
|
-
let attributes = this.parsePartialAttributes(fullTag, partialName);
|
|
187
|
-
partialTags.push({
|
|
188
|
-
start: tagStart,
|
|
189
|
-
end: tagEnd,
|
|
190
|
-
name: partialName,
|
|
191
|
-
attributes: attributes,
|
|
192
|
-
fullTag: fullTag
|
|
193
|
-
});
|
|
194
|
-
searchPos = tagEnd;
|
|
195
|
-
}
|
|
196
|
-
return partialTags;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
findPartialTagEnd(template, tagStart)
|
|
200
|
-
{
|
|
201
|
-
let inQuotes = false;
|
|
202
|
-
let quoteChar = '';
|
|
203
|
-
let selfCloseTag = '/>';
|
|
204
|
-
let openCloseTag = '</partial>';
|
|
205
|
-
for(let i = tagStart; i < template.length; i++){
|
|
206
|
-
let char = template[i];
|
|
207
|
-
if(!inQuotes && ('"' === char || "'" === char)){
|
|
208
|
-
inQuotes = true;
|
|
209
|
-
quoteChar = char;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if(inQuotes && char === quoteChar && '\\' !== template[i - 1]){
|
|
213
|
-
inQuotes = false;
|
|
214
|
-
quoteChar = '';
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if(!inQuotes){
|
|
218
|
-
if(template.substring(i, i + selfCloseTag.length) === selfCloseTag){
|
|
219
|
-
return i + selfCloseTag.length;
|
|
220
|
-
}
|
|
221
|
-
if('>' === char){
|
|
222
|
-
let closeIndex = template.indexOf(openCloseTag, i);
|
|
223
|
-
if(-1 !== closeIndex){
|
|
224
|
-
return closeIndex + openCloseTag.length;
|
|
225
|
-
}
|
|
226
|
-
return i + 1;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return -1;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
parsePartialAttributes(fullTag, partialName)
|
|
234
|
-
{
|
|
235
|
-
let namePattern = 'name=' + this.getQuotePattern(fullTag, partialName);
|
|
236
|
-
let nameIndex = fullTag.indexOf(namePattern);
|
|
237
|
-
if(-1 === nameIndex){
|
|
238
|
-
return {};
|
|
239
|
-
}
|
|
240
|
-
let attributesStart = nameIndex + namePattern.length;
|
|
241
|
-
let attributesEnd = fullTag.lastIndexOf('/>');
|
|
242
|
-
if(-1 === attributesEnd){
|
|
243
|
-
attributesEnd = fullTag.lastIndexOf('</partial>');
|
|
244
|
-
}
|
|
245
|
-
if(-1 === attributesEnd){
|
|
246
|
-
attributesEnd = fullTag.lastIndexOf('>');
|
|
247
|
-
}
|
|
248
|
-
if(-1 === attributesEnd || attributesEnd <= attributesStart){
|
|
249
|
-
return {};
|
|
250
|
-
}
|
|
251
|
-
let attributesString = fullTag.substring(attributesStart, attributesEnd).trim();
|
|
252
|
-
return this.extractAttributesObject(attributesString);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
getQuotePattern(fullTag, partialName)
|
|
256
|
-
{
|
|
257
|
-
if(fullTag.includes('name="' + partialName + '"')){
|
|
258
|
-
return '"' + partialName + '"';
|
|
259
|
-
}
|
|
260
|
-
if(fullTag.includes("name='" + partialName + "'")){
|
|
261
|
-
return "'" + partialName + "'";
|
|
262
|
-
}
|
|
263
|
-
return '"' + partialName + '"';
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
extractAttributesObject(attributesString)
|
|
267
|
-
{
|
|
268
|
-
if(!attributesString){
|
|
269
|
-
return {};
|
|
270
|
-
}
|
|
271
|
-
let attributes = {};
|
|
272
|
-
let valueRegex = /(\w+)=(['"])((?:(?!\2)[^\\]|\\.)*)(\2)/g;
|
|
273
|
-
for(let match of attributesString.matchAll(valueRegex)){
|
|
274
|
-
attributes[match[1]] = match[3];
|
|
275
|
-
}
|
|
276
|
-
let booleanRegex = /\b(\w+)(?!\s*=)/g;
|
|
277
|
-
for(let match of attributesString.matchAll(booleanRegex)){
|
|
278
|
-
if(!sc.hasOwn(attributes, match[1])){
|
|
279
|
-
attributes[match[1]] = true;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return attributes;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
loadPartialTemplate(partialName)
|
|
286
|
-
{
|
|
287
|
-
if(!this.getPartials){
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
let partials = this.getPartials(this.currentDomain);
|
|
291
|
-
if(sc.hasOwn(partials, partialName)){
|
|
292
|
-
return partials[partialName];
|
|
293
|
-
}
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async processLoopCollection(template, startMatch, tableName, filtersJson, queryOptionsJson)
|
|
298
|
-
{
|
|
299
|
-
let startPos = startMatch.index;
|
|
300
|
-
let startEnd = startPos + startMatch[0].length;
|
|
301
|
-
let endMatch = this.getLoopCollectionEndRegex(tableName).exec(template.substring(startEnd));
|
|
302
|
-
if(!endMatch){
|
|
303
|
-
Logger.warning('No matching end tag found for collection: '+tableName);
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
let endPos = startEnd + endMatch.index;
|
|
307
|
-
let loopContent = template.substring(startEnd, endPos);
|
|
308
|
-
let collectionData = await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson);
|
|
309
|
-
let renderedContent = await this.renderCollectionLoop(loopContent, collectionData);
|
|
310
|
-
return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async renderCollectionLoop(loopContent, collectionData)
|
|
314
|
-
{
|
|
315
|
-
let renderedContent = '';
|
|
316
|
-
for(let row of collectionData){
|
|
317
|
-
renderedContent += await this.processPartialsInLoop(loopContent, row);
|
|
318
|
-
}
|
|
319
|
-
return renderedContent;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async processPartialsInLoop(content, rowData)
|
|
323
|
-
{
|
|
324
|
-
let processedContent = content;
|
|
325
|
-
let partialTags = this.findAllPartialTags(content);
|
|
326
|
-
for(let i = partialTags.length - 1; i >= 0; i--){
|
|
327
|
-
let tag = partialTags[i];
|
|
328
|
-
let partialContent = this.loadPartialTemplate(tag.name);
|
|
329
|
-
if(!partialContent){
|
|
330
|
-
Logger.warning('Partial template not found: ' + tag.name);
|
|
331
|
-
processedContent = processedContent.substring(0, tag.start) + '' + processedContent.substring(tag.end);
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if(sc.hasOwn(tag.attributes, 'row')){
|
|
335
|
-
let renderedPartial = this.renderEngine.render(partialContent, {row: rowData}, this.getPartials(this.currentDomain));
|
|
336
|
-
processedContent = processedContent.substring(0, tag.start) + renderedPartial + processedContent.substring(tag.end);
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
340
|
-
let renderData = { vars: tag.attributes };
|
|
341
|
-
let partials = {[tag.name]: partialContent};
|
|
342
|
-
processedContent = processedContent.substring(0, tag.start) +
|
|
343
|
-
this.renderEngine.render(wrapperTemplate, renderData, partials) +
|
|
344
|
-
processedContent.substring(tag.end);
|
|
345
|
-
}
|
|
346
|
-
return this.renderEngine.render(processedContent, {row: rowData}, this.getPartials(this.currentDomain));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
extractFieldValues(collectionData, fieldName)
|
|
350
|
-
{
|
|
351
|
-
let fieldValues = '';
|
|
352
|
-
for(let row of collectionData){
|
|
353
|
-
fieldValues += sc.get(row, fieldName, '');
|
|
354
|
-
}
|
|
355
|
-
return fieldValues;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async fetchEntityForTemplate(tableName, identifier, identifierField)
|
|
359
|
-
{
|
|
360
|
-
let entity = this.dataServer.getEntity(tableName);
|
|
361
|
-
if(!entity){
|
|
362
|
-
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
return await entity.loadOneBy(identifierField, identifier);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
convertJsObjectToJson(jsObjectString)
|
|
369
|
-
{
|
|
370
|
-
if(!jsObjectString || '' === jsObjectString.trim()){
|
|
371
|
-
return '';
|
|
372
|
-
}
|
|
373
|
-
return jsObjectString.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson)
|
|
377
|
-
{
|
|
378
|
-
let entity = this.dataServer.getEntity(tableName);
|
|
379
|
-
if(!entity){
|
|
380
|
-
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
381
|
-
return [];
|
|
382
|
-
}
|
|
383
|
-
let filters = false;
|
|
384
|
-
if(filtersJson && '' !== filtersJson.trim()){
|
|
385
|
-
let convertedFiltersJson = this.convertJsObjectToJson(filtersJson);
|
|
386
|
-
filters = sc.parseJson(convertedFiltersJson, false);
|
|
387
|
-
if(!filters){
|
|
388
|
-
Logger.warning('Invalid filters JSON: '+filtersJson);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
let originalState = this.preserveEntityState(entity);
|
|
392
|
-
let queryOptions = {};
|
|
393
|
-
if(queryOptionsJson && '' !== queryOptionsJson.trim()){
|
|
394
|
-
let convertedOptionsJson = this.convertJsObjectToJson(queryOptionsJson);
|
|
395
|
-
queryOptions = sc.parseJson(convertedOptionsJson, {});
|
|
396
|
-
if(!queryOptions){
|
|
397
|
-
Logger.warning('Invalid query options JSON: '+queryOptionsJson);
|
|
398
|
-
queryOptions = {};
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
this.applyQueryOptions(entity, queryOptions);
|
|
402
|
-
let result = filters ? await entity.load(filters) : await entity.loadAll();
|
|
403
|
-
this.restoreEntityState(entity, originalState);
|
|
404
|
-
return result;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
preserveEntityState(entity)
|
|
408
|
-
{
|
|
409
|
-
return {
|
|
410
|
-
limit: entity.limit,
|
|
411
|
-
offset: entity.offset,
|
|
412
|
-
sortBy: entity.sortBy,
|
|
413
|
-
sortDirection: entity.sortDirection
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
restoreEntityState(entity, originalState)
|
|
418
|
-
{
|
|
419
|
-
entity.limit = originalState.limit;
|
|
420
|
-
entity.offset = originalState.offset;
|
|
421
|
-
entity.sortBy = originalState.sortBy;
|
|
422
|
-
entity.sortDirection = originalState.sortDirection;
|
|
99
|
+
return this.partialsTransformer.findAllPartialTags(template);
|
|
423
100
|
}
|
|
424
101
|
|
|
425
|
-
|
|
102
|
+
loadPartialTemplate(partialName, domain)
|
|
426
103
|
{
|
|
427
|
-
|
|
428
|
-
entity.limit = queryOptions.limit;
|
|
429
|
-
}
|
|
430
|
-
if(sc.hasOwn(queryOptions, 'offset')){
|
|
431
|
-
entity.offset = queryOptions.offset;
|
|
432
|
-
}
|
|
433
|
-
if(sc.hasOwn(queryOptions, 'sortBy')){
|
|
434
|
-
entity.sortBy = queryOptions.sortBy;
|
|
435
|
-
}
|
|
436
|
-
if(sc.hasOwn(queryOptions, 'sortDirection')){
|
|
437
|
-
entity.sortDirection = queryOptions.sortDirection;
|
|
438
|
-
}
|
|
104
|
+
return this.partialsTransformer.loadPartialTemplate(partialName, domain);
|
|
439
105
|
}
|
|
440
106
|
|
|
441
107
|
}
|
|
442
108
|
|
|
443
|
-
module.exports.TemplateEngine = TemplateEngine;
|
|
109
|
+
module.exports.TemplateEngine = TemplateEngine;
|
package/lib/templates-list.js
CHANGED
|
@@ -18,6 +18,8 @@ module.exports.TemplatesList = {
|
|
|
18
18
|
sideBarItem: 'sidebar-item.html',
|
|
19
19
|
paginationLink: 'pagination-link.html',
|
|
20
20
|
defaultCopyRight: 'default-copyright.html',
|
|
21
|
+
cacheCleanButton: 'cache-clean-button.html',
|
|
22
|
+
clearAllCacheButton: 'clear-all-cache-button.html',
|
|
21
23
|
fields: {
|
|
22
24
|
view: {
|
|
23
25
|
text: 'text.html',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
1
|
-- Default homepage:
|
|
3
2
|
|
|
4
|
-
-- Create a default
|
|
3
|
+
-- Create a default route first
|
|
4
|
+
REPLACE INTO `routes` (`id`, `path`, `router`, `cache_ttl_seconds`, `enabled`, `created_at`) VALUES (1, '/home', 'cmsPages', 3600, 1, NOW());
|
|
5
|
+
|
|
6
|
+
-- Create a default homepage with route_id reference
|
|
5
7
|
REPLACE INTO `cms_pages` (
|
|
6
|
-
`id`, `title`, `content`, `template`, `meta_title`, `meta_description`,
|
|
8
|
+
`id`, `title`, `content`, `template`, `route_id`, `meta_title`, `meta_description`,
|
|
7
9
|
`canonical_url`, `meta_robots`, `meta_og_title`, `meta_og_description`,
|
|
8
10
|
`meta_og_image`, `meta_twitter_card_type`, `status`, `locale`, `publish_date`, `expire_date`, `created_at`
|
|
9
11
|
) VALUES (
|
|
@@ -11,6 +13,7 @@ REPLACE INTO `cms_pages` (
|
|
|
11
13
|
'Home',
|
|
12
14
|
'<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>',
|
|
13
15
|
NULL,
|
|
16
|
+
1,
|
|
14
17
|
'Home - Reldens CMS',
|
|
15
18
|
'Welcome to Reldens CMS',
|
|
16
19
|
NULL,
|
|
@@ -25,6 +28,3 @@ REPLACE INTO `cms_pages` (
|
|
|
25
28
|
NULL,
|
|
26
29
|
NOW()
|
|
27
30
|
);
|
|
28
|
-
|
|
29
|
-
-- Create a default route to the homepage
|
|
30
|
-
REPLACE INTO `routes` (`id`, `path`, `router`, `cms_page_id`, `cache_ttl_seconds`, `enabled`, `created_at`) VALUES (1, '/home', 'cmsPages', 1, 3600, 1, NOW());
|
package/migrations/install.sql
CHANGED
|
@@ -5,7 +5,6 @@ CREATE TABLE IF NOT EXISTS `routes` (
|
|
|
5
5
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
6
6
|
`path` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
7
7
|
`router` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
8
|
-
`cms_page_id` INT UNSIGNED NOT NULL,
|
|
9
8
|
`cache_ttl_seconds` INT UNSIGNED NULL DEFAULT 3600,
|
|
10
9
|
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
11
10
|
`domain` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
@@ -30,32 +29,34 @@ CREATE TABLE IF NOT EXISTS `cms_categories` (
|
|
|
30
29
|
|
|
31
30
|
CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
32
31
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
33
|
-
`title` VARCHAR(255)
|
|
34
|
-
`content` LONGTEXT
|
|
35
|
-
`
|
|
36
|
-
`
|
|
37
|
-
`
|
|
38
|
-
`layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'default',
|
|
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',
|
|
39
37
|
`category_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
38
|
+
`route_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
40
39
|
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
41
|
-
`meta_title` VARCHAR(255)
|
|
42
|
-
`meta_description` VARCHAR(255)
|
|
43
|
-
`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',
|
|
44
43
|
`meta_theme_color` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
45
|
-
`meta_og_title` VARCHAR(255)
|
|
46
|
-
`meta_og_description` VARCHAR(255)
|
|
47
|
-
`meta_og_image` VARCHAR(255)
|
|
48
|
-
`meta_twitter_card_type` VARCHAR(255)
|
|
49
|
-
`canonical_url` VARCHAR(255)
|
|
50
|
-
`status` VARCHAR(20)
|
|
51
|
-
`locale` VARCHAR(10)
|
|
52
|
-
`publish_date` TIMESTAMP NULL,
|
|
53
|
-
`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,
|
|
54
53
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
55
54
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
56
55
|
PRIMARY KEY (`id`) USING BTREE,
|
|
57
56
|
INDEX `FK_cms_pages_cms_categories` (`category_id`) USING BTREE,
|
|
58
|
-
|
|
57
|
+
INDEX `FK_cms_pages_routes` (`route_id`) USING BTREE,
|
|
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 NO ACTION
|
|
59
60
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
60
61
|
|
|
61
62
|
CREATE TABLE IF NOT EXISTS `cms_blocks` (
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reldens/cms",
|
|
3
3
|
"scope": "@reldens",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"description": "Reldens - CMS",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"url": "https://github.com/damian-pastorini/reldens-cms/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@reldens/server-utils": "^0.
|
|
37
|
-
"@reldens/storage": "^0.
|
|
36
|
+
"@reldens/server-utils": "^0.20.0",
|
|
37
|
+
"@reldens/storage": "^0.60.0",
|
|
38
38
|
"@reldens/utils": "^0.50.0",
|
|
39
|
-
"dotenv": "^
|
|
39
|
+
"dotenv": "^17.0.0",
|
|
40
40
|
"mustache": "^4.2.0"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/templates/page.html
CHANGED
|
@@ -2,23 +2,40 @@
|
|
|
2
2
|
<html lang="{{locale}}">
|
|
3
3
|
<head>
|
|
4
4
|
<title>{{meta_title}}</title>
|
|
5
|
+
<meta name="language" content="{{locale}}"/>
|
|
5
6
|
<meta charset="utf-8"/>
|
|
6
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover"/>
|
|
7
|
-
<meta name="description" content="{{meta_description}}">
|
|
8
8
|
<meta name="robots" content="{{meta_robots}}">
|
|
9
9
|
<meta name="theme-color" content="{{meta_theme_color}}"/>
|
|
10
|
+
{{#meta_description}}
|
|
11
|
+
<meta name="description" content="{{meta_description}}"/>
|
|
12
|
+
{{/meta_description}}
|
|
10
13
|
<meta property="og:title" content="{{meta_og_title}}"/>
|
|
14
|
+
{{#meta_og_description}}
|
|
11
15
|
<meta property="og:description" content="{{meta_og_description}}"/>
|
|
16
|
+
{{/meta_og_description}}
|
|
17
|
+
{{#meta_og_image}}
|
|
12
18
|
<meta property="og:image" content="{{meta_og_image}}"/>
|
|
19
|
+
{{/meta_og_image}}
|
|
20
|
+
{{#meta_twitter_card_type}}
|
|
13
21
|
<meta name="twitter:card" content="{{meta_twitter_card_type}}"/>
|
|
14
|
-
|
|
22
|
+
{{/meta_twitter_card_type}}
|
|
23
|
+
{{#publish_date}}
|
|
15
24
|
<meta name="publish-date" content="{{publish_date}}"/>
|
|
25
|
+
{{/publish_date}}
|
|
26
|
+
{{#expire_date}}
|
|
16
27
|
<meta name="expire-date" content="{{expire_date}}"/>
|
|
28
|
+
{{/expire_date}}
|
|
29
|
+
{{#canonical_url}}
|
|
17
30
|
<link rel="canonical" href="{{canonical_url}}"/>
|
|
31
|
+
{{/canonical_url}}
|
|
32
|
+
<!-- CSS and JS -->
|
|
18
33
|
<link href="/css/styles.css" rel="stylesheet"/>
|
|
34
|
+
<script src="/js/cookie-consent.js"></script>
|
|
19
35
|
</head>
|
|
20
36
|
<body class="{{siteHandle}}">
|
|
21
37
|
{{&content}}
|
|
38
|
+
{{>cookie-consent}}
|
|
22
39
|
<script type="text/javascript" defer src="/js/scripts.js"></script>
|
|
23
40
|
</body>
|
|
24
41
|
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="col-lg-6 mt-2 mb-2">
|
|
2
|
+
<div class="entry-item">
|
|
3
|
+
<div class="pic use-circle">
|
|
4
|
+
<img src="{{&row.json_data.image}}" alt="{{row.title}}"/>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="entry-info">
|
|
7
|
+
<a href="{{&row.routes.path}}"{{targetBlank}}>
|
|
8
|
+
<h4>{{ row.title }}</h4>
|
|
9
|
+
{{&row.json_data.shortDescription}}
|
|
10
|
+
<p>{{&row.json_data.date}}</p>
|
|
11
|
+
</a>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|