@reldens/cms 0.12.0 → 0.13.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 +47 -11
- package/bin/reldens-cms-generate-entities.js +74 -0
- package/lib/frontend.js +9 -39
- package/lib/installer.js +3 -4
- package/lib/manager.js +1 -1
- package/lib/template-engine.js +175 -0
- package/package.json +4 -3
- package/templates/layouts/default.html +3 -8
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
19
19
|
- **Template fallback system** (domain → default → base)
|
|
20
20
|
- **Layout system** with body content layouts and page wrappers
|
|
21
21
|
- **Reusable content blocks** with `{{ entity() }}` template functions
|
|
22
|
+
- **Collection rendering** with filtering and loop support
|
|
22
23
|
- **Entity access control** for public/private content
|
|
23
24
|
- **Static asset serving** with Express integration as default
|
|
24
25
|
- **Template engine** with Mustache integration as default
|
|
@@ -44,7 +45,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
44
45
|
|
|
45
46
|
### - Configuration & Architecture
|
|
46
47
|
- **Environment-based configuration** (.env file)
|
|
47
|
-
- **Modular service architecture** (Frontend, AdminManager, DataServer)
|
|
48
|
+
- **Modular service architecture** (Frontend, AdminManager, DataServer, TemplateEngine)
|
|
48
49
|
- **Event-driven system** with hooks for customization
|
|
49
50
|
- **Extensible authentication** (database users or custom callbacks)
|
|
50
51
|
- **File security** with path validation and dangerous key filtering
|
|
@@ -127,15 +128,43 @@ const cms = new Manager({
|
|
|
127
128
|
## Enhanced Templating System
|
|
128
129
|
|
|
129
130
|
### Template Functions
|
|
130
|
-
Templates
|
|
131
|
+
Templates support dynamic content blocks, entity rendering, and collections:
|
|
132
|
+
|
|
133
|
+
**Single Entity Rendering:**
|
|
131
134
|
```html
|
|
132
|
-
<!-- Render content blocks -->
|
|
133
|
-
{{ entity('
|
|
134
|
-
{{ entity('
|
|
135
|
+
<!-- Render by any identifier field (like 'name' for content blocks) -->
|
|
136
|
+
{{ entity('cmsBlocks', 'header-main', 'name') }}
|
|
137
|
+
{{ entity('cmsBlocks', 'sidebar-left', 'name') }}
|
|
135
138
|
|
|
136
|
-
<!-- Render
|
|
139
|
+
<!-- Render by ID (default identifier) -->
|
|
137
140
|
{{ entity('products', '123') }}
|
|
138
|
-
{{ entity('
|
|
141
|
+
{{ entity('cmsPages', '1') }}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Single Field Collections:**
|
|
145
|
+
```html
|
|
146
|
+
<!-- Extract and concatenate a single field from multiple records -->
|
|
147
|
+
{{ collection('cmsBlocks', {"status": "active", "category": "navigation"}, 'content') }}
|
|
148
|
+
{{ collection('products', {"featured": true}, 'title') }}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Loop Collections:**
|
|
152
|
+
```html
|
|
153
|
+
<!-- Loop through records with full template rendering -->
|
|
154
|
+
{{ #collection('cmsBlocks', {"status": "active"}) }}
|
|
155
|
+
<div class="block">
|
|
156
|
+
<h3>{{row.title}}</h3>
|
|
157
|
+
<div class="content">{{row.content}}</div>
|
|
158
|
+
</div>
|
|
159
|
+
{{ /collection('cmsBlocks') }}
|
|
160
|
+
|
|
161
|
+
{{ #collection('products', {"category": "electronics"}) }}
|
|
162
|
+
<div class="product">
|
|
163
|
+
<h4>{{row.name}}</h4>
|
|
164
|
+
<p>Price: ${{row.price}}</p>
|
|
165
|
+
<img src="{{row.image}}" alt="{{row.name}}">
|
|
166
|
+
</div>
|
|
167
|
+
{{ /collection('products') }}
|
|
139
168
|
```
|
|
140
169
|
|
|
141
170
|
### Layout System
|
|
@@ -159,13 +188,13 @@ The CMS uses a two-tier layout system:
|
|
|
159
188
|
|
|
160
189
|
**layouts/default.html** - Body content only:
|
|
161
190
|
```html
|
|
162
|
-
{{ entity('
|
|
191
|
+
{{ entity('cmsBlocks', 'header-main', 'name') }}
|
|
163
192
|
|
|
164
193
|
<main id="main" class="main-container">
|
|
165
194
|
<div class="container">
|
|
166
195
|
<div class="row">
|
|
167
196
|
<div class="col-md-3">
|
|
168
|
-
{{ entity('
|
|
197
|
+
{{ entity('cmsBlocks', 'sidebar-left', 'name') }}
|
|
169
198
|
</div>
|
|
170
199
|
<div class="col-md-9">
|
|
171
200
|
{{{content}}}
|
|
@@ -174,7 +203,7 @@ The CMS uses a two-tier layout system:
|
|
|
174
203
|
</div>
|
|
175
204
|
</main>
|
|
176
205
|
|
|
177
|
-
{{ entity('
|
|
206
|
+
{{ entity('cmsBlocks', 'footer-main', 'name') }}
|
|
178
207
|
```
|
|
179
208
|
|
|
180
209
|
Pages can use different layouts by setting the `layout` field in `cms_pages`:
|
|
@@ -306,7 +335,14 @@ The installer provides checkboxes for:
|
|
|
306
335
|
- `handleRequest(req, res)` - Main request handler
|
|
307
336
|
- `findRouteByPath(path)` - Database route lookup
|
|
308
337
|
- `findEntityByPath(path)` - Entity-based URL handling
|
|
309
|
-
|
|
338
|
+
|
|
339
|
+
### TemplateEngine Class
|
|
340
|
+
- `render(template, data, partials)` - Main template rendering with enhanced functions
|
|
341
|
+
- `processEntityFunctions(template)` - Process {{ entity() }} functions
|
|
342
|
+
- `processSingleFieldCollections(template)` - Process single field collections
|
|
343
|
+
- `processLoopCollections(template)` - Process loop collections
|
|
344
|
+
- `fetchEntityForTemplate(tableName, identifier, identifierField)` - Load single entity
|
|
345
|
+
- `fetchCollectionForTemplate(tableName, filtersJson)` - Load entity collections
|
|
310
346
|
|
|
311
347
|
### AdminManager Class
|
|
312
348
|
- `setupAdmin()` - Initialize admin panel
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* Reldens - CMS - Generate Entities CLI
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { Manager } = require('../index');
|
|
10
|
+
const { Logger } = require('@reldens/utils');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
class CmsEntitiesGenerator
|
|
14
|
+
{
|
|
15
|
+
|
|
16
|
+
constructor()
|
|
17
|
+
{
|
|
18
|
+
this.args = process.argv.slice(2);
|
|
19
|
+
this.projectRoot = process.cwd();
|
|
20
|
+
this.isOverride = this.args.includes('--override');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async run()
|
|
24
|
+
{
|
|
25
|
+
if(this.isOverride){
|
|
26
|
+
let confirmed = await this.confirmOverride();
|
|
27
|
+
if(!confirmed){
|
|
28
|
+
Logger.info('Operation cancelled by user.');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let manager = new Manager({projectRoot: this.projectRoot});
|
|
33
|
+
if(!manager.isInstalled()){
|
|
34
|
+
Logger.error('CMS is not installed. Please run installation first.');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
Logger.debug('Reldens CMS Manager instance created for entities generation.');
|
|
38
|
+
await manager.initializeDataServer();
|
|
39
|
+
let success = await manager.installer.generateEntities(manager.dataServer, this.isOverride);
|
|
40
|
+
if(!success){
|
|
41
|
+
Logger.error('Entities generation failed.');
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
Logger.info('Entities generation completed successfully!');
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async confirmOverride()
|
|
49
|
+
{
|
|
50
|
+
let rl = readline.createInterface({
|
|
51
|
+
input: process.stdin,
|
|
52
|
+
output: process.stdout
|
|
53
|
+
});
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
Logger.warning('WARNING: Using --override will regenerate ALL entities and overwrite existing files.');
|
|
56
|
+
rl.question('Are you sure you want to continue? (yes/no): ', (answer) => {
|
|
57
|
+
rl.close();
|
|
58
|
+
resolve('yes' === answer.toLowerCase() || 'y' === answer.toLowerCase());
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let generator = new CmsEntitiesGenerator();
|
|
66
|
+
generator.run().then((success) => {
|
|
67
|
+
if(!success){
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
Logger.critical('Error during entities generation: '+error.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
package/lib/frontend.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { FileHandler } = require('@reldens/server-utils');
|
|
8
8
|
const { Logger, sc } = require('@reldens/utils');
|
|
9
|
+
const { TemplateEngine } = require('./template-engine');
|
|
9
10
|
|
|
10
11
|
class Frontend
|
|
11
12
|
{
|
|
@@ -27,6 +28,7 @@ class Frontend
|
|
|
27
28
|
this.domainPartialsCache = new Map();
|
|
28
29
|
this.domainTemplatesMap = new Map();
|
|
29
30
|
this.entityAccessCache = new Map();
|
|
31
|
+
this.templateEngine = false;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
async initialize()
|
|
@@ -51,6 +53,10 @@ class Frontend
|
|
|
51
53
|
Logger.error('Public folder not found: '+this.publicPath);
|
|
52
54
|
return false;
|
|
53
55
|
}
|
|
56
|
+
this.templateEngine = new TemplateEngine({
|
|
57
|
+
renderEngine: this.renderEngine,
|
|
58
|
+
dataServer: this.dataServer
|
|
59
|
+
});
|
|
54
60
|
await this.loadPartials();
|
|
55
61
|
await this.setupDomainTemplates();
|
|
56
62
|
await this.loadEntityAccessRules();
|
|
@@ -323,42 +329,6 @@ class Frontend
|
|
|
323
329
|
return this.findTemplatePath(templatePath, domain);
|
|
324
330
|
}
|
|
325
331
|
|
|
326
|
-
async renderEnhanced(template, data, partials)
|
|
327
|
-
{
|
|
328
|
-
return this.renderEngine.render(
|
|
329
|
-
await this.processCustomTemplateFunctions(template),
|
|
330
|
-
data,
|
|
331
|
-
partials
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async processCustomTemplateFunctions(template)
|
|
336
|
-
{
|
|
337
|
-
let entityRegex = /\{\{\s*entity\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*\)\s*\}\}/g;
|
|
338
|
-
let processedTemplate = template;
|
|
339
|
-
for(let match of template.matchAll(entityRegex)){
|
|
340
|
-
let tableName = match[1];
|
|
341
|
-
let identifier = match[2];
|
|
342
|
-
let entityData = await this.fetchEntityForTemplate(tableName, identifier);
|
|
343
|
-
processedTemplate = processedTemplate.replace(match[0], sc.get(entityData, 'content', ''));
|
|
344
|
-
}
|
|
345
|
-
return processedTemplate;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async fetchEntityForTemplate(tableName, identifier)
|
|
349
|
-
{
|
|
350
|
-
let entity = this.dataServer.getEntity(tableName);
|
|
351
|
-
if(!entity){
|
|
352
|
-
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
353
|
-
return {};
|
|
354
|
-
}
|
|
355
|
-
let result = await entity.loadOneBy('name', identifier);
|
|
356
|
-
if(!result){
|
|
357
|
-
result = await entity.loadById(identifier);
|
|
358
|
-
}
|
|
359
|
-
return result || {};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
332
|
async renderRoute(route, domain, res)
|
|
363
333
|
{
|
|
364
334
|
if(!route.router || !route.content_id){
|
|
@@ -409,7 +379,7 @@ class Frontend
|
|
|
409
379
|
Logger.error('Failed to read template: ' + templatePath);
|
|
410
380
|
return false;
|
|
411
381
|
}
|
|
412
|
-
return await this.
|
|
382
|
+
return await this.templateEngine.render(template, data, this.getPartialsForDomain(domain));
|
|
413
383
|
}
|
|
414
384
|
|
|
415
385
|
async renderWithLayout(content, data, layoutName, domain, res)
|
|
@@ -419,7 +389,7 @@ class Frontend
|
|
|
419
389
|
if(layoutPath){
|
|
420
390
|
let layoutTemplate = FileHandler.readFile(layoutPath);
|
|
421
391
|
if(layoutTemplate){
|
|
422
|
-
layoutContent = await this.
|
|
392
|
+
layoutContent = await this.templateEngine.render(
|
|
423
393
|
layoutTemplate,
|
|
424
394
|
Object.assign({}, data, {
|
|
425
395
|
content: sc.get(content, 'content', '')
|
|
@@ -440,7 +410,7 @@ class Frontend
|
|
|
440
410
|
return res.send(layoutContent);
|
|
441
411
|
}
|
|
442
412
|
return res.send(
|
|
443
|
-
await this.
|
|
413
|
+
await this.templateEngine.render(
|
|
444
414
|
pageTemplate,
|
|
445
415
|
Object.assign({}, data, {
|
|
446
416
|
content: layoutContent,
|
package/lib/installer.js
CHANGED
|
@@ -234,8 +234,7 @@ class Installer
|
|
|
234
234
|
Logger.error('SQL file "'+fileName+'" not found.');
|
|
235
235
|
return '/?error=sql-file-not-found&file-name='+fileName;
|
|
236
236
|
}
|
|
237
|
-
|
|
238
|
-
if(!queryResult){
|
|
237
|
+
if(!await dbDriver.rawQuery(sqlFileContent)){
|
|
239
238
|
Logger.error('SQL file "'+fileName+'" raw execution failed.');
|
|
240
239
|
return '/?error=sql-file-execution-error&file-name='+fileName;
|
|
241
240
|
}
|
|
@@ -243,9 +242,9 @@ class Installer
|
|
|
243
242
|
return '';
|
|
244
243
|
}
|
|
245
244
|
|
|
246
|
-
async generateEntities(server)
|
|
245
|
+
async generateEntities(server, isOverride = false)
|
|
247
246
|
{
|
|
248
|
-
let generator = new EntitiesGenerator({server, projectPath: this.projectRoot});
|
|
247
|
+
let generator = new EntitiesGenerator({server, projectPath: this.projectRoot, isOverride});
|
|
249
248
|
let success = await generator.generate();
|
|
250
249
|
if(!success){
|
|
251
250
|
Logger.error('Entities generation failed.');
|
package/lib/manager.js
CHANGED
|
@@ -55,7 +55,7 @@ class Manager
|
|
|
55
55
|
this.defaultDomain = sc.get(props, 'defaultDomain', (process.env.RELDENS_DEFAULT_DOMAIN || ''));
|
|
56
56
|
this.domainMapping = sc.get(props, 'domainMapping', sc.toJson(process.env.RELDENS_DOMAIN_MAPPING));
|
|
57
57
|
this.siteKeyMapping = sc.get(props, 'siteKeyMapping', sc.toJson(process.env.RELDENS_SITE_KEY_MAPPING));
|
|
58
|
-
this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.
|
|
58
|
+
this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.template']);
|
|
59
59
|
this.app = sc.get(props, 'app', false);
|
|
60
60
|
this.appServer = sc.get(props, 'appServer', false);
|
|
61
61
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - TemplateEngine
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class TemplateEngine
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
15
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async render(template, data, partials)
|
|
19
|
+
{
|
|
20
|
+
if(!this.renderEngine){
|
|
21
|
+
Logger.error('Render engine not provided');
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
if(!sc.isFunction(this.renderEngine.render)){
|
|
25
|
+
Logger.error('Render engine does not contain a render method');
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
return this.renderEngine.render(
|
|
29
|
+
await this.processLoopCollections(
|
|
30
|
+
await this.processSingleFieldCollections(
|
|
31
|
+
await this.processEntityFunctions(template)
|
|
32
|
+
)
|
|
33
|
+
),
|
|
34
|
+
data,
|
|
35
|
+
partials
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getEntityRegex()
|
|
40
|
+
{
|
|
41
|
+
return /\{\{\s*entity\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?\)\s*\}\}/g;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getSingleFieldCollectionRegex()
|
|
45
|
+
{
|
|
46
|
+
return /\{\{\s*collection\(\s*['"]([^'"]+)['"]\s*,\s*(\{[^}]*\})\s*,\s*['"]([^'"]+)['"]\s*\)\s*\}\}/g;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getLoopCollectionStartRegex()
|
|
50
|
+
{
|
|
51
|
+
return /\{\{\s*#collection\(\s*['"]([^'"]+)['"]\s*,\s*(\{[^}]*\})\s*\)\s*\}\}/g;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getLoopCollectionEndRegex(tableName)
|
|
55
|
+
{
|
|
56
|
+
return new RegExp('\\{\\{\\s*\\/collection\\(\\s*[\'"]'
|
|
57
|
+
+this.escapeRegex(tableName)
|
|
58
|
+
+'[\'"]\\s*\\)\\s*\\}\\}'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async processEntityFunctions(template)
|
|
63
|
+
{
|
|
64
|
+
let processedTemplate = template;
|
|
65
|
+
for(let match of template.matchAll(this.getEntityRegex())){
|
|
66
|
+
let tableName = match[1];
|
|
67
|
+
let identifier = match[2];
|
|
68
|
+
let identifierField = match[3] || 'id';
|
|
69
|
+
let entityData = await this.fetchEntityForTemplate(tableName, identifier, identifierField);
|
|
70
|
+
processedTemplate = processedTemplate.replace(match[0], sc.get(entityData, 'content', ''));
|
|
71
|
+
}
|
|
72
|
+
return processedTemplate;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async processSingleFieldCollections(template)
|
|
76
|
+
{
|
|
77
|
+
let processedTemplate = template;
|
|
78
|
+
for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
|
|
79
|
+
let tableName = match[1];
|
|
80
|
+
let filtersJson = match[2];
|
|
81
|
+
let fieldName = match[3];
|
|
82
|
+
processedTemplate = processedTemplate.replace(
|
|
83
|
+
match[0],
|
|
84
|
+
this.extractFieldValues(
|
|
85
|
+
await this.fetchCollectionForTemplate(tableName, filtersJson),
|
|
86
|
+
fieldName
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return processedTemplate;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async processLoopCollections(template)
|
|
94
|
+
{
|
|
95
|
+
let processedTemplate = template;
|
|
96
|
+
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
97
|
+
for(let i = matches.length - 1; i >= 0; i--){
|
|
98
|
+
let startMatch = matches[i];
|
|
99
|
+
let tableName = startMatch[1];
|
|
100
|
+
let filtersJson = startMatch[2];
|
|
101
|
+
let loopResult = await this.processLoopCollection(processedTemplate, startMatch, tableName, filtersJson);
|
|
102
|
+
if(false !== loopResult){
|
|
103
|
+
processedTemplate = loopResult;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return processedTemplate;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async processLoopCollection(template, startMatch, tableName, filtersJson)
|
|
110
|
+
{
|
|
111
|
+
let startPos = startMatch.index;
|
|
112
|
+
let startEnd = startPos + startMatch[0].length;
|
|
113
|
+
let endMatch = this.getLoopCollectionEndRegex(tableName).exec(template.substring(startEnd));
|
|
114
|
+
if(!endMatch){
|
|
115
|
+
Logger.warning('No matching end tag found for collection: '+tableName);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
let endPos = startEnd + endMatch.index;
|
|
119
|
+
let renderedContent = await this.renderCollectionLoop(
|
|
120
|
+
template.substring(startEnd, endPos),
|
|
121
|
+
await this.fetchCollectionForTemplate(tableName, filtersJson)
|
|
122
|
+
);
|
|
123
|
+
return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async renderCollectionLoop(loopContent, collectionData)
|
|
127
|
+
{
|
|
128
|
+
let renderedContent = '';
|
|
129
|
+
for(let row of collectionData){
|
|
130
|
+
renderedContent += this.renderEngine.render(loopContent, {row}, {});
|
|
131
|
+
}
|
|
132
|
+
return renderedContent;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
extractFieldValues(collectionData, fieldName)
|
|
136
|
+
{
|
|
137
|
+
let fieldValues = '';
|
|
138
|
+
for(let row of collectionData){
|
|
139
|
+
fieldValues += sc.get(row, fieldName, '');
|
|
140
|
+
}
|
|
141
|
+
return fieldValues;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
escapeRegex(string)
|
|
145
|
+
{
|
|
146
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async fetchEntityForTemplate(tableName, identifier, identifierField)
|
|
150
|
+
{
|
|
151
|
+
let entity = this.dataServer.getEntity(tableName);
|
|
152
|
+
if(!entity){
|
|
153
|
+
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return await entity.loadOneBy(identifierField, identifier);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async fetchCollectionForTemplate(tableName, filtersJson)
|
|
160
|
+
{
|
|
161
|
+
let entity = this.dataServer.getEntity(tableName);
|
|
162
|
+
if(!entity){
|
|
163
|
+
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
let filters = sc.toJson(filtersJson);
|
|
167
|
+
if(!filters){
|
|
168
|
+
return await entity.loadAll();
|
|
169
|
+
}
|
|
170
|
+
return await entity.load(filters);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports.TemplateEngine = TemplateEngine;
|
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.13.0",
|
|
5
5
|
"description": "Reldens - CMS",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"node": ">=20.0.0"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"reldens-cms": "bin/reldens-cms.js"
|
|
14
|
+
"reldens-cms": "bin/reldens-cms.js",
|
|
15
|
+
"reldens-cms-generate-entities": "bin/reldens-cms-generate-entities.js"
|
|
15
16
|
},
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@reldens/server-utils": "^0.18.0",
|
|
36
|
-
"@reldens/storage": "^0.
|
|
37
|
+
"@reldens/storage": "^0.49.0",
|
|
37
38
|
"@reldens/utils": "^0.49.0",
|
|
38
39
|
"dotenv": "^16.5.0",
|
|
39
40
|
"mustache": "^4.2.0"
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
{{>header}}
|
|
3
|
-
|
|
4
|
-
{{ entity('cmsBlocks', 'header-main') }}
|
|
1
|
+
{{ entity('cmsBlocks', 'header-main', 'name') }}
|
|
5
2
|
|
|
6
3
|
<main id="main" class="main-container">
|
|
7
4
|
<div class="container">
|
|
8
5
|
<div class="row">
|
|
9
6
|
<div class="col-md-3">
|
|
10
|
-
{{ entity('cmsBlocks', 'sidebar-left') }}
|
|
7
|
+
{{ entity('cmsBlocks', 'sidebar-left', 'name') }}
|
|
11
8
|
</div>
|
|
12
9
|
<div class="col-md-9">
|
|
13
10
|
{{{content}}}
|
|
@@ -16,6 +13,4 @@
|
|
|
16
13
|
</div>
|
|
17
14
|
</main>
|
|
18
15
|
|
|
19
|
-
{{ entity('cmsBlocks', 'footer-main') }}
|
|
20
|
-
|
|
21
|
-
{{>footer}}
|
|
16
|
+
{{ entity('cmsBlocks', 'footer-main', 'name') }}
|