@reldens/cms 0.18.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.
@@ -4,8 +4,12 @@
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 { Logger, sc } = require('@reldens/utils');
9
13
 
10
14
  class TemplateEngine
11
15
  {
@@ -15,29 +19,52 @@ class TemplateEngine
15
19
  this.renderEngine = sc.get(props, 'renderEngine', false);
16
20
  this.dataServer = sc.get(props, 'dataServer', false);
17
21
  this.getPartials = sc.get(props, 'getPartials', false);
18
- this.currentDomain = '';
19
- this.jsonFieldsParser = new JsonFieldsParser({
20
- entitiesConfig: sc.get(props, 'entitiesConfig', {})
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)
21
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
+ ];
22
54
  }
23
55
 
24
- setCurrentDomain(domain)
25
- {
26
- this.currentDomain = domain;
27
- }
28
-
29
- async processAllTemplateFunctions(template)
56
+ async processAllTemplateFunctions(template, domain, req)
30
57
  {
31
- return await this.processCustomPartials(
32
- await this.processLoopCollections(
33
- await this.processSingleFieldCollections(
34
- await this.processEntityFunctions(template)
35
- )
36
- )
37
- );
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;
38
65
  }
39
66
 
40
- async render(template, data, partials)
67
+ async render(template, data, partials, domain, req)
41
68
  {
42
69
  if(!this.renderEngine){
43
70
  Logger.error('Render engine not provided');
@@ -48,7 +75,7 @@ class TemplateEngine
48
75
  return '';
49
76
  }
50
77
  return this.renderEngine.render(
51
- this.unescapeHtml(await this.processAllTemplateFunctions(template)),
78
+ this.unescapeHtml(await this.processAllTemplateFunctions(template, domain, req)),
52
79
  data,
53
80
  partials
54
81
  );
@@ -67,427 +94,14 @@ class TemplateEngine
67
94
  .replace(/&/g, '&');
68
95
  }
69
96
 
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
97
  findAllPartialTags(template)
194
98
  {
195
- let partialTags = [];
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;
222
- }
223
-
224
- findPartialTagEnd(template, tagStart)
225
- {
226
- let inQuotes = false;
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;
99
+ return this.partialsTransformer.findAllPartialTags(template);
475
100
  }
476
101
 
477
- applyQueryOptions(entity, queryOptions)
102
+ loadPartialTemplate(partialName, domain)
478
103
  {
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
- }
104
+ return this.partialsTransformer.loadPartialTemplate(partialName, domain);
491
105
  }
492
106
 
493
107
  }
@@ -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',
@@ -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) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
33
- `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
34
- `json_data` JSON NULL,
35
- `template` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
36
- `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',
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) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
41
- `meta_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
42
- `meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
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) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
45
- `meta_og_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
46
- `meta_og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
47
- `meta_twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
48
- `canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
49
- `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
50
- `locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
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 SET NULL
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` (
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.18.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.19.0",
37
- "@reldens/storage": "^0.59.0",
36
+ "@reldens/server-utils": "^0.20.0",
37
+ "@reldens/storage": "^0.60.0",
38
38
  "@reldens/utils": "^0.50.0",
39
- "dotenv": "^16.5.0",
39
+ "dotenv": "^17.0.0",
40
40
  "mustache": "^4.2.0"
41
41
  }
42
42
  }
@@ -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
- <meta name="language" content="{{locale}}"/>
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>