@reldens/cms 0.16.0 → 0.18.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/admin/reldens-admin-client.css +11 -0
- package/admin/templates/cache-clean-button.html +4 -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 +256 -0
- package/lib/admin-manager/router-contents.js +576 -0
- package/lib/admin-manager/router.js +208 -0
- package/lib/admin-manager.js +114 -990
- package/lib/cache/add-cache-button-subscriber.js +101 -0
- package/lib/cache/cache-manager.js +129 -0
- package/lib/cache/cache-routes-handler.js +76 -0
- package/lib/cms-pages-route-manager.js +31 -19
- package/lib/frontend.js +192 -52
- package/lib/installer.js +5 -2
- package/lib/json-fields-parser.js +74 -0
- package/lib/manager.js +7 -1
- package/lib/template-engine.js +79 -27
- package/lib/templates-list.js +1 -0
- package/migrations/default-homepage.sql +6 -6
- package/migrations/install.sql +4 -3
- package/package.json +2 -2
package/lib/template-engine.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
const { JsonFieldsParser } = require('./json-fields-parser');
|
|
8
9
|
|
|
9
10
|
class TemplateEngine
|
|
10
11
|
{
|
|
@@ -15,6 +16,9 @@ class TemplateEngine
|
|
|
15
16
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
17
|
this.getPartials = sc.get(props, 'getPartials', false);
|
|
17
18
|
this.currentDomain = '';
|
|
19
|
+
this.jsonFieldsParser = new JsonFieldsParser({
|
|
20
|
+
entitiesConfig: sc.get(props, 'entitiesConfig', {})
|
|
21
|
+
});
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
setCurrentDomain(domain)
|
|
@@ -70,12 +74,12 @@ class TemplateEngine
|
|
|
70
74
|
|
|
71
75
|
getSingleFieldCollectionRegex()
|
|
72
76
|
{
|
|
73
|
-
return /<collection\s+
|
|
77
|
+
return /<collection\s+([^>]+)\/>/g;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
getLoopCollectionStartRegex()
|
|
77
81
|
{
|
|
78
|
-
return /<collection\s+
|
|
82
|
+
return /<collection\s+([^>]+)>/g;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
getLoopCollectionEndRegex()
|
|
@@ -83,6 +87,13 @@ class TemplateEngine
|
|
|
83
87
|
return new RegExp('<\\/collection>');
|
|
84
88
|
}
|
|
85
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
|
+
|
|
86
97
|
async processEntityFunctions(template)
|
|
87
98
|
{
|
|
88
99
|
let processedTemplate = template;
|
|
@@ -108,14 +119,19 @@ class TemplateEngine
|
|
|
108
119
|
{
|
|
109
120
|
let processedTemplate = template;
|
|
110
121
|
for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
|
|
111
|
-
let
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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');
|
|
115
131
|
processedTemplate = processedTemplate.replace(
|
|
116
132
|
match[0],
|
|
117
133
|
this.extractFieldValues(
|
|
118
|
-
await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson),
|
|
134
|
+
await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson, relationsString),
|
|
119
135
|
fieldName
|
|
120
136
|
)
|
|
121
137
|
);
|
|
@@ -129,12 +145,21 @@ class TemplateEngine
|
|
|
129
145
|
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
130
146
|
for(let i = matches.length - 1; i >= 0; i--){
|
|
131
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');
|
|
132
156
|
let loopResult = await this.processLoopCollection(
|
|
133
157
|
processedTemplate,
|
|
134
158
|
startMatch,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
tableName,
|
|
160
|
+
filtersJson,
|
|
161
|
+
relationsString,
|
|
162
|
+
queryOptionsJson
|
|
138
163
|
);
|
|
139
164
|
if(loopResult){
|
|
140
165
|
processedTemplate = loopResult;
|
|
@@ -152,7 +177,7 @@ class TemplateEngine
|
|
|
152
177
|
let partialContent = this.loadPartialTemplate(tag.name);
|
|
153
178
|
if(!partialContent){
|
|
154
179
|
Logger.warning('Partial template not found: ' + tag.name);
|
|
155
|
-
processedTemplate = processedTemplate.substring(0, tag.start)
|
|
180
|
+
processedTemplate = processedTemplate.substring(0, tag.start)+''+processedTemplate.substring(tag.end);
|
|
156
181
|
continue;
|
|
157
182
|
}
|
|
158
183
|
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
@@ -168,18 +193,18 @@ class TemplateEngine
|
|
|
168
193
|
findAllPartialTags(template)
|
|
169
194
|
{
|
|
170
195
|
let partialTags = [];
|
|
171
|
-
let
|
|
172
|
-
let
|
|
173
|
-
for(let tagStart = template.indexOf(
|
|
196
|
+
let pos = 0;
|
|
197
|
+
let partial = '<partial';
|
|
198
|
+
for(let tagStart = template.indexOf(partial, pos); -1 !== tagStart; tagStart=template.indexOf(partial, pos)){
|
|
174
199
|
let tagEnd = this.findPartialTagEnd(template, tagStart);
|
|
175
200
|
if(-1 === tagEnd){
|
|
176
|
-
|
|
201
|
+
pos = tagStart + partial.length;
|
|
177
202
|
continue;
|
|
178
203
|
}
|
|
179
204
|
let fullTag = template.substring(tagStart, tagEnd);
|
|
180
205
|
let nameMatch = fullTag.match(/name=["']([^"']+)["']/);
|
|
181
206
|
if(!nameMatch){
|
|
182
|
-
|
|
207
|
+
pos = tagStart + partial.length;
|
|
183
208
|
continue;
|
|
184
209
|
}
|
|
185
210
|
let partialName = nameMatch[1];
|
|
@@ -191,7 +216,7 @@ class TemplateEngine
|
|
|
191
216
|
attributes: attributes,
|
|
192
217
|
fullTag: fullTag
|
|
193
218
|
});
|
|
194
|
-
|
|
219
|
+
pos = tagEnd;
|
|
195
220
|
}
|
|
196
221
|
return partialTags;
|
|
197
222
|
}
|
|
@@ -294,7 +319,7 @@ class TemplateEngine
|
|
|
294
319
|
return false;
|
|
295
320
|
}
|
|
296
321
|
|
|
297
|
-
async processLoopCollection(template, startMatch, tableName, filtersJson, queryOptionsJson)
|
|
322
|
+
async processLoopCollection(template, startMatch, tableName, filtersJson, relationsString, queryOptionsJson)
|
|
298
323
|
{
|
|
299
324
|
let startPos = startMatch.index;
|
|
300
325
|
let startEnd = startPos + startMatch[0].length;
|
|
@@ -305,7 +330,12 @@ class TemplateEngine
|
|
|
305
330
|
}
|
|
306
331
|
let endPos = startEnd + endMatch.index;
|
|
307
332
|
let loopContent = template.substring(startEnd, endPos);
|
|
308
|
-
let collectionData = await this.fetchCollectionForTemplate(
|
|
333
|
+
let collectionData = await this.fetchCollectionForTemplate(
|
|
334
|
+
tableName,
|
|
335
|
+
filtersJson,
|
|
336
|
+
queryOptionsJson,
|
|
337
|
+
relationsString
|
|
338
|
+
);
|
|
309
339
|
let renderedContent = await this.renderCollectionLoop(loopContent, collectionData);
|
|
310
340
|
return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
|
|
311
341
|
}
|
|
@@ -328,12 +358,13 @@ class TemplateEngine
|
|
|
328
358
|
let partialContent = this.loadPartialTemplate(tag.name);
|
|
329
359
|
if(!partialContent){
|
|
330
360
|
Logger.warning('Partial template not found: ' + tag.name);
|
|
331
|
-
processedContent = processedContent.substring(0, tag.start)
|
|
361
|
+
processedContent = processedContent.substring(0, tag.start)+''+processedContent.substring(tag.end);
|
|
332
362
|
continue;
|
|
333
363
|
}
|
|
334
364
|
if(sc.hasOwn(tag.attributes, 'row')){
|
|
335
|
-
|
|
336
|
-
|
|
365
|
+
processedContent = processedContent.substring(0, tag.start)
|
|
366
|
+
+this.renderEngine.render(partialContent, {row: rowData}, this.getPartials(this.currentDomain))
|
|
367
|
+
+processedContent.substring(tag.end);
|
|
337
368
|
continue;
|
|
338
369
|
}
|
|
339
370
|
let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
|
|
@@ -362,7 +393,14 @@ class TemplateEngine
|
|
|
362
393
|
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
363
394
|
return false;
|
|
364
395
|
}
|
|
365
|
-
|
|
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
|
+
);
|
|
366
404
|
}
|
|
367
405
|
|
|
368
406
|
convertJsObjectToJson(jsObjectString)
|
|
@@ -373,7 +411,15 @@ class TemplateEngine
|
|
|
373
411
|
return jsObjectString.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
|
|
374
412
|
}
|
|
375
413
|
|
|
376
|
-
|
|
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)
|
|
377
423
|
{
|
|
378
424
|
let entity = this.dataServer.getEntity(tableName);
|
|
379
425
|
if(!entity){
|
|
@@ -399,9 +445,15 @@ class TemplateEngine
|
|
|
399
445
|
}
|
|
400
446
|
}
|
|
401
447
|
this.applyQueryOptions(entity, queryOptions);
|
|
402
|
-
let
|
|
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();
|
|
403
452
|
this.restoreEntityState(entity, originalState);
|
|
404
|
-
return
|
|
453
|
+
return this.jsonFieldsParser.parseJsonFields(
|
|
454
|
+
result,
|
|
455
|
+
this.jsonFieldsParser.getJsonFieldsForEntity(tableName)
|
|
456
|
+
);
|
|
405
457
|
}
|
|
406
458
|
|
|
407
459
|
preserveEntityState(entity)
|
|
@@ -440,4 +492,4 @@ class TemplateEngine
|
|
|
440
492
|
|
|
441
493
|
}
|
|
442
494
|
|
|
443
|
-
module.exports.TemplateEngine = TemplateEngine;
|
|
495
|
+
module.exports.TemplateEngine = TemplateEngine;
|
package/lib/templates-list.js
CHANGED
|
@@ -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',
|
|
@@ -32,11 +31,11 @@ CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
|
32
31
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
33
32
|
`title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
34
33
|
`content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
35
|
-
`markdown` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
36
34
|
`json_data` JSON NULL,
|
|
37
35
|
`template` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
38
36
|
`layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'default',
|
|
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
40
|
`meta_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
42
41
|
`meta_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
@@ -55,7 +54,9 @@ CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
|
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 SET NULL
|
|
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.18.0",
|
|
5
5
|
"description": "Reldens - CMS",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@reldens/server-utils": "^0.19.0",
|
|
37
|
-
"@reldens/storage": "^0.
|
|
37
|
+
"@reldens/storage": "^0.59.0",
|
|
38
38
|
"@reldens/utils": "^0.50.0",
|
|
39
39
|
"dotenv": "^16.5.0",
|
|
40
40
|
"mustache": "^4.2.0"
|