@reldens/cms 0.15.0 → 0.16.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 +150 -34
- package/admin/reldens-admin-client.css +28 -23
- package/admin/reldens-admin-client.js +7 -0
- package/admin/templates/edit.html +3 -1
- package/admin/templates/fields/view/textarea.html +1 -0
- package/admin/templates/sections/editForm/cms-pages.html +15 -0
- package/admin/templates/sections/viewForm/cms-pages.html +15 -0
- package/admin/templates/view.html +1 -0
- package/bin/reldens-cms-generate-entities.js +47 -3
- package/bin/reldens-cms.js +26 -8
- package/install/js/installer.js +5 -0
- package/install/success.html +1 -1
- package/lib/admin-manager.js +80 -34
- package/lib/cms-pages-route-manager.js +105 -0
- package/lib/frontend.js +53 -50
- package/lib/installer.js +41 -20
- package/lib/manager.js +48 -9
- package/lib/template-engine.js +310 -42
- package/lib/templates-list.js +9 -0
- package/migrations/default-blocks.sql +1 -1
- package/migrations/default-entity-access.sql +2 -2
- package/migrations/default-homepage.sql +24 -4
- package/migrations/install.sql +31 -35
- package/package.json +3 -3
- package/templates/index.js.dist +3 -3
- package/templates/js/scripts.js +5 -0
- package/templates/layouts/default.html +4 -4
- package/templates/page.html +14 -10
- package/templates/partials/footer.html +2 -23
- package/templates/partials/header.html +2 -35
package/lib/template-engine.js
CHANGED
|
@@ -13,6 +13,24 @@ class TemplateEngine
|
|
|
13
13
|
{
|
|
14
14
|
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
15
15
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
|
+
this.getPartials = sc.get(props, 'getPartials', false);
|
|
17
|
+
this.currentDomain = '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setCurrentDomain(domain)
|
|
21
|
+
{
|
|
22
|
+
this.currentDomain = domain;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async processAllTemplateFunctions(template)
|
|
26
|
+
{
|
|
27
|
+
return await this.processCustomPartials(
|
|
28
|
+
await this.processLoopCollections(
|
|
29
|
+
await this.processSingleFieldCollections(
|
|
30
|
+
await this.processEntityFunctions(template)
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
);
|
|
16
34
|
}
|
|
17
35
|
|
|
18
36
|
async render(template, data, partials)
|
|
@@ -26,37 +44,43 @@ class TemplateEngine
|
|
|
26
44
|
return '';
|
|
27
45
|
}
|
|
28
46
|
return this.renderEngine.render(
|
|
29
|
-
await this.
|
|
30
|
-
await this.processSingleFieldCollections(
|
|
31
|
-
await this.processEntityFunctions(template)
|
|
32
|
-
)
|
|
33
|
-
),
|
|
47
|
+
this.unescapeHtml(await this.processAllTemplateFunctions(template)),
|
|
34
48
|
data,
|
|
35
49
|
partials
|
|
36
50
|
);
|
|
37
51
|
}
|
|
38
52
|
|
|
53
|
+
unescapeHtml(text)
|
|
54
|
+
{
|
|
55
|
+
return text
|
|
56
|
+
.replace(/"/g, '"')
|
|
57
|
+
.replace(/'/g, "'")
|
|
58
|
+
.replace(/'/g, "'")
|
|
59
|
+
.replace(/=/g, '=')
|
|
60
|
+
.replace(///g, '/')
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>')
|
|
63
|
+
.replace(/&/g, '&');
|
|
64
|
+
}
|
|
65
|
+
|
|
39
66
|
getEntityRegex()
|
|
40
67
|
{
|
|
41
|
-
return
|
|
68
|
+
return /<entity\s+name="([^"]+)"(?:\s+field="([^"]+)"\s+value="([^"]+)"|\s+id="([^"]+)")?\s*\/?>/g;
|
|
42
69
|
}
|
|
43
70
|
|
|
44
71
|
getSingleFieldCollectionRegex()
|
|
45
72
|
{
|
|
46
|
-
return
|
|
73
|
+
return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?\s+field=(['"])([^'"]+)\5(?:\s+data=(['"])([^'"]*)\7)?\s*\/>/g;
|
|
47
74
|
}
|
|
48
75
|
|
|
49
76
|
getLoopCollectionStartRegex()
|
|
50
77
|
{
|
|
51
|
-
return
|
|
78
|
+
return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?(?:\s+data=(['"])([^'"]*)\5)?\s*>/g;
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
getLoopCollectionEndRegex(
|
|
81
|
+
getLoopCollectionEndRegex()
|
|
55
82
|
{
|
|
56
|
-
return new RegExp('
|
|
57
|
-
+this.escapeRegex(tableName)
|
|
58
|
-
+'[\'"]\\s*\\)\\s*\\}\\}'
|
|
59
|
-
);
|
|
83
|
+
return new RegExp('<\\/collection>');
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
async processEntityFunctions(template)
|
|
@@ -64,10 +88,18 @@ class TemplateEngine
|
|
|
64
88
|
let processedTemplate = template;
|
|
65
89
|
for(let match of template.matchAll(this.getEntityRegex())){
|
|
66
90
|
let tableName = match[1];
|
|
67
|
-
let
|
|
68
|
-
let
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
);
|
|
71
103
|
}
|
|
72
104
|
return processedTemplate;
|
|
73
105
|
}
|
|
@@ -76,13 +108,14 @@ class TemplateEngine
|
|
|
76
108
|
{
|
|
77
109
|
let processedTemplate = template;
|
|
78
110
|
for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
|
|
79
|
-
let tableName = match[
|
|
80
|
-
let filtersJson = match
|
|
81
|
-
let fieldName = match[
|
|
111
|
+
let tableName = match[2];
|
|
112
|
+
let filtersJson = sc.get(match, '4', '{}');
|
|
113
|
+
let fieldName = match[6];
|
|
114
|
+
let queryOptionsJson = sc.get(match, '8', '{}');
|
|
82
115
|
processedTemplate = processedTemplate.replace(
|
|
83
116
|
match[0],
|
|
84
117
|
this.extractFieldValues(
|
|
85
|
-
await this.fetchCollectionForTemplate(tableName, filtersJson),
|
|
118
|
+
await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson),
|
|
86
119
|
fieldName
|
|
87
120
|
)
|
|
88
121
|
);
|
|
@@ -96,17 +129,172 @@ class TemplateEngine
|
|
|
96
129
|
let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
|
|
97
130
|
for(let i = matches.length - 1; i >= 0; i--){
|
|
98
131
|
let startMatch = matches[i];
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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){
|
|
103
140
|
processedTemplate = loopResult;
|
|
104
141
|
}
|
|
105
142
|
}
|
|
106
143
|
return processedTemplate;
|
|
107
144
|
}
|
|
108
145
|
|
|
109
|
-
async
|
|
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
|
+
findAllPartialTags(template)
|
|
169
|
+
{
|
|
170
|
+
let partialTags = [];
|
|
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)
|
|
110
298
|
{
|
|
111
299
|
let startPos = startMatch.index;
|
|
112
300
|
let startEnd = startPos + startMatch[0].length;
|
|
@@ -116,10 +304,9 @@ class TemplateEngine
|
|
|
116
304
|
return false;
|
|
117
305
|
}
|
|
118
306
|
let endPos = startEnd + endMatch.index;
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
307
|
+
let loopContent = template.substring(startEnd, endPos);
|
|
308
|
+
let collectionData = await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson);
|
|
309
|
+
let renderedContent = await this.renderCollectionLoop(loopContent, collectionData);
|
|
123
310
|
return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
|
|
124
311
|
}
|
|
125
312
|
|
|
@@ -127,11 +314,38 @@ class TemplateEngine
|
|
|
127
314
|
{
|
|
128
315
|
let renderedContent = '';
|
|
129
316
|
for(let row of collectionData){
|
|
130
|
-
renderedContent += this.
|
|
317
|
+
renderedContent += await this.processPartialsInLoop(loopContent, row);
|
|
131
318
|
}
|
|
132
319
|
return renderedContent;
|
|
133
320
|
}
|
|
134
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
|
+
|
|
135
349
|
extractFieldValues(collectionData, fieldName)
|
|
136
350
|
{
|
|
137
351
|
let fieldValues = '';
|
|
@@ -141,11 +355,6 @@ class TemplateEngine
|
|
|
141
355
|
return fieldValues;
|
|
142
356
|
}
|
|
143
357
|
|
|
144
|
-
escapeRegex(string)
|
|
145
|
-
{
|
|
146
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
358
|
async fetchEntityForTemplate(tableName, identifier, identifierField)
|
|
150
359
|
{
|
|
151
360
|
let entity = this.dataServer.getEntity(tableName);
|
|
@@ -156,20 +365,79 @@ class TemplateEngine
|
|
|
156
365
|
return await entity.loadOneBy(identifierField, identifier);
|
|
157
366
|
}
|
|
158
367
|
|
|
159
|
-
|
|
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)
|
|
160
377
|
{
|
|
161
378
|
let entity = this.dataServer.getEntity(tableName);
|
|
162
379
|
if(!entity){
|
|
163
380
|
Logger.warning('Entity not found in dataServer: '+tableName);
|
|
164
381
|
return [];
|
|
165
382
|
}
|
|
166
|
-
let filters =
|
|
167
|
-
if(
|
|
168
|
-
|
|
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;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
applyQueryOptions(entity, queryOptions)
|
|
426
|
+
{
|
|
427
|
+
if(sc.hasOwn(queryOptions, 'limit')){
|
|
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;
|
|
169
438
|
}
|
|
170
|
-
return await entity.load(filters);
|
|
171
439
|
}
|
|
172
440
|
|
|
173
441
|
}
|
|
174
442
|
|
|
175
|
-
module.exports.TemplateEngine = TemplateEngine;
|
|
443
|
+
module.exports.TemplateEngine = TemplateEngine;
|
package/lib/templates-list.js
CHANGED
|
@@ -21,6 +21,7 @@ module.exports.TemplatesList = {
|
|
|
21
21
|
fields: {
|
|
22
22
|
view: {
|
|
23
23
|
text: 'text.html',
|
|
24
|
+
textarea: 'textarea.html',
|
|
24
25
|
image: 'image.html',
|
|
25
26
|
images: 'images.html',
|
|
26
27
|
link: 'link.html',
|
|
@@ -37,5 +38,13 @@ module.exports.TemplatesList = {
|
|
|
37
38
|
button: 'button.html',
|
|
38
39
|
file: 'file.html'
|
|
39
40
|
}
|
|
41
|
+
},
|
|
42
|
+
sections: {
|
|
43
|
+
viewForm: {
|
|
44
|
+
'cms-pages': 'cms-pages.html',
|
|
45
|
+
},
|
|
46
|
+
editForm: {
|
|
47
|
+
'cms-pages': 'cms-pages.html',
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
-- Default CMS blocks:
|
|
3
3
|
|
|
4
|
-
REPLACE INTO `cms_blocks` (`name`, `title`, `content`, `
|
|
4
|
+
REPLACE INTO `cms_blocks` (`name`, `title`, `content`, `enabled`) VALUES
|
|
5
5
|
('header-main', 'Main Header', '{{>header}}', 1),
|
|
6
6
|
('sidebar-left', 'Left Sidebar', '<aside class="sidebar-left">{{>sidebar}}</aside>', 1),
|
|
7
7
|
('sidebar-right', 'Right Sidebar', '<aside class="sidebar-right">{{>sidebar}}</aside>', 1),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
-- Default entity access rules:
|
|
3
3
|
|
|
4
|
-
REPLACE INTO `
|
|
4
|
+
REPLACE INTO `entities_access` (`entity_name`, `is_public`, `allowed_operations`) VALUES
|
|
5
5
|
('cms_pages', TRUE, '["read"]'),
|
|
6
6
|
('routes', FALSE, '[]'),
|
|
7
7
|
('users', FALSE, '[]'),
|
|
8
8
|
('cms_blocks', FALSE, '[]'),
|
|
9
|
-
('
|
|
9
|
+
('entities_access', FALSE, '[]');
|
|
@@ -2,9 +2,29 @@
|
|
|
2
2
|
-- Default homepage:
|
|
3
3
|
|
|
4
4
|
-- Create a default homepage route if not exists
|
|
5
|
-
REPLACE INTO `cms_pages` (
|
|
6
|
-
|
|
5
|
+
REPLACE INTO `cms_pages` (
|
|
6
|
+
`id`, `title`, `content`, `template`, `meta_title`, `meta_description`,
|
|
7
|
+
`canonical_url`, `meta_robots`, `meta_og_title`, `meta_og_description`,
|
|
8
|
+
`meta_og_image`, `meta_twitter_card_type`, `status`, `locale`, `publish_date`, `expire_date`, `created_at`
|
|
9
|
+
) VALUES (
|
|
10
|
+
1,
|
|
11
|
+
'Home',
|
|
12
|
+
'<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>',
|
|
13
|
+
NULL,
|
|
14
|
+
'Home - Reldens CMS',
|
|
15
|
+
'Welcome to Reldens CMS',
|
|
16
|
+
NULL,
|
|
17
|
+
'index,follow',
|
|
18
|
+
'Home - Reldens CMS',
|
|
19
|
+
'Welcome to the Reldens CMS homepage',
|
|
20
|
+
NULL,
|
|
21
|
+
'summary',
|
|
22
|
+
'published',
|
|
23
|
+
'en',
|
|
24
|
+
NOW(),
|
|
25
|
+
NULL,
|
|
26
|
+
NOW()
|
|
27
|
+
);
|
|
7
28
|
|
|
8
29
|
-- Create a default route to the homepage
|
|
9
|
-
REPLACE INTO `routes` (`id`, `path`, `router`, `
|
|
10
|
-
(1, '/home', 'cmsPages', 1, 'Home', 'Welcome to Reldens CMS', 'published', NOW());
|
|
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,34 +5,27 @@ 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
|
-
`
|
|
9
|
-
`title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
10
|
-
`meta_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
11
|
-
`canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
12
|
-
`meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
|
|
13
|
-
`og_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
14
|
-
`og_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
15
|
-
`og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
16
|
-
`twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
|
|
17
|
-
`status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
|
18
|
-
`publish_at` TIMESTAMP NULL,
|
|
19
|
-
`locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
|
|
8
|
+
`cms_page_id` INT UNSIGNED NOT NULL,
|
|
20
9
|
`cache_ttl_seconds` INT UNSIGNED NULL DEFAULT 3600,
|
|
10
|
+
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
11
|
+
`domain` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
21
12
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
22
13
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
23
14
|
PRIMARY KEY (`id`) USING BTREE,
|
|
24
|
-
UNIQUE KEY `
|
|
15
|
+
UNIQUE KEY `path_domain` (`path`, `domain`) USING BTREE
|
|
25
16
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
26
17
|
|
|
27
|
-
CREATE TABLE IF NOT EXISTS `
|
|
18
|
+
CREATE TABLE IF NOT EXISTS `cms_categories` (
|
|
28
19
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
29
|
-
`
|
|
30
|
-
`
|
|
31
|
-
`
|
|
20
|
+
`name` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
21
|
+
`title` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
22
|
+
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
23
|
+
`parent_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
32
24
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
33
25
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
34
26
|
PRIMARY KEY (`id`) USING BTREE,
|
|
35
|
-
|
|
27
|
+
INDEX `FK_cms_categories_cms_categories` (`parent_id`) USING BTREE,
|
|
28
|
+
CONSTRAINT `FK_cms_categories_cms_categories` FOREIGN KEY (`parent_id`) REFERENCES `cms_categories` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION
|
|
36
29
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
37
30
|
|
|
38
31
|
CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
@@ -43,21 +36,26 @@ CREATE TABLE IF NOT EXISTS `cms_pages` (
|
|
|
43
36
|
`json_data` JSON NULL,
|
|
44
37
|
`template` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
45
38
|
`layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'default',
|
|
46
|
-
`
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
`
|
|
54
|
-
`
|
|
55
|
-
`
|
|
56
|
-
`
|
|
39
|
+
`category_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
40
|
+
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
41
|
+
`meta_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
42
|
+
`meta_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
43
|
+
`meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
|
|
44
|
+
`meta_theme_color` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
|
|
45
|
+
`meta_og_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
46
|
+
`meta_og_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
47
|
+
`meta_og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
48
|
+
`meta_twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
|
|
49
|
+
`canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
50
|
+
`status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
|
51
|
+
`locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
|
|
52
|
+
`publish_date` TIMESTAMP NULL,
|
|
53
|
+
`expire_date` TIMESTAMP NULL,
|
|
57
54
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
58
55
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
59
56
|
PRIMARY KEY (`id`) USING BTREE,
|
|
60
|
-
|
|
57
|
+
INDEX `FK_cms_pages_cms_categories` (`category_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
|
|
61
59
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
62
60
|
|
|
63
61
|
CREATE TABLE IF NOT EXISTS `cms_blocks` (
|
|
@@ -65,19 +63,17 @@ CREATE TABLE IF NOT EXISTS `cms_blocks` (
|
|
|
65
63
|
`name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
66
64
|
`title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
67
65
|
`content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
68
|
-
`
|
|
69
|
-
`variables` JSON NULL,
|
|
70
|
-
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
|
|
66
|
+
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
|
|
71
67
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
72
68
|
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
73
69
|
PRIMARY KEY (`id`) USING BTREE,
|
|
74
70
|
UNIQUE KEY `name` (`name`) USING BTREE
|
|
75
71
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
76
72
|
|
|
77
|
-
CREATE TABLE IF NOT EXISTS `
|
|
73
|
+
CREATE TABLE IF NOT EXISTS `entities_access` (
|
|
78
74
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
79
75
|
`entity_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
80
|
-
`is_public`
|
|
76
|
+
`is_public` TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
|
81
77
|
`allowed_operations` JSON NULL DEFAULT ('["read"]'),
|
|
82
78
|
`access_rules` JSON NULL,
|
|
83
79
|
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
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.16.0",
|
|
5
5
|
"description": "Reldens - CMS",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,8 +33,8 @@
|
|
|
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.19.0",
|
|
37
|
+
"@reldens/storage": "^0.56.0",
|
|
38
38
|
"@reldens/utils": "^0.50.0",
|
|
39
39
|
"dotenv": "^16.5.0",
|
|
40
40
|
"mustache": "^4.2.0"
|