@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.
- package/README.md +92 -9
- package/admin/reldens-admin-client.css +46 -2
- package/admin/reldens-admin-client.js +24 -0
- package/admin/templates/clear-all-cache-button.html +18 -0
- package/lib/admin-manager/contents-builder.js +7 -6
- package/lib/admin-manager/router-contents.js +58 -16
- package/lib/admin-manager-validator.js +2 -1
- package/lib/admin-manager.js +4 -2
- package/lib/admin-translations.js +9 -1
- package/lib/cache/add-cache-button-subscriber.js +53 -5
- package/lib/cache/cache-manager.js +47 -8
- package/lib/cache/cache-routes-handler.js +23 -0
- package/lib/cms-pages-route-manager.js +16 -4
- package/lib/frontend.js +188 -111
- package/lib/manager.js +42 -3
- package/lib/pagination-handler.js +243 -0
- package/lib/search-renderer.js +116 -0
- package/lib/search.js +344 -0
- package/lib/template-engine/collections-single-transformer.js +53 -0
- package/lib/template-engine/collections-transformer-base.js +84 -0
- package/lib/template-engine/collections-transformer.js +353 -0
- package/lib/template-engine/entities-transformer.js +65 -0
- package/lib/template-engine/partials-transformer.js +171 -0
- package/lib/template-engine.js +49 -435
- package/lib/templates-list.js +1 -0
- package/migrations/install.sql +18 -18
- package/package.json +4 -4
- package/templates/page.html +19 -2
- package/templates/partials/entriesListView.html +14 -0
- package/templates/partials/pagedCollection.html +33 -0
package/README.md
CHANGED
|
@@ -157,18 +157,18 @@ Templates support dynamic content blocks, entity rendering, and collections with
|
|
|
157
157
|
```html
|
|
158
158
|
<!-- Loop through records with full template rendering -->
|
|
159
159
|
<collection name="cmsBlocks" filters="{status: 'active'}">
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
<div class="block">
|
|
161
|
+
<h3>{{row.title}}</h3>
|
|
162
|
+
<div class="content">{{row.content}}</div>
|
|
163
|
+
</div>
|
|
164
164
|
</collection>
|
|
165
165
|
|
|
166
166
|
<collection name="articles" filters="{category: 'technology'}">
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
<div class="article">
|
|
168
|
+
<h4>{{row.title}}</h4>
|
|
169
|
+
<p>{{row.summary}}</p>
|
|
170
|
+
<img src="{{row.featured_image}}" alt="{{row.title}}">
|
|
171
|
+
</div>
|
|
172
172
|
</collection>
|
|
173
173
|
|
|
174
174
|
<!-- With pagination and sorting -->
|
|
@@ -178,8 +178,91 @@ Templates support dynamic content blocks, entity rendering, and collections with
|
|
|
178
178
|
<p>{{row.summary}}</p>
|
|
179
179
|
</div>
|
|
180
180
|
</collection>
|
|
181
|
+
|
|
182
|
+
<!-- Paginated collections with navigation -->
|
|
183
|
+
<collection name="articles"
|
|
184
|
+
filters="{featured: true}"
|
|
185
|
+
data="{limit: 10, sortBy: 'created_at', sortDirection: 'desc'}"
|
|
186
|
+
pagination="articles-1"
|
|
187
|
+
container="pagedCollection"
|
|
188
|
+
prevPages="2"
|
|
189
|
+
nextPages="2">
|
|
190
|
+
<div class="article-card">
|
|
191
|
+
<h4>{{row.title}}</h4>
|
|
192
|
+
<p>{{row.summary}}</p>
|
|
193
|
+
<span class="date">{{row.created_at}}</span>
|
|
194
|
+
</div>
|
|
195
|
+
</collection>
|
|
196
|
+
|
|
197
|
+
<!-- Multiple paginated collections on the same page -->
|
|
198
|
+
<collection name="news"
|
|
199
|
+
filters="{category: 'technology'}"
|
|
200
|
+
data="{limit: 5, sortBy: 'published_at'}"
|
|
201
|
+
pagination="news-tech"
|
|
202
|
+
container="customPager">
|
|
203
|
+
<article>{{row.title}}</article>
|
|
204
|
+
</collection>
|
|
205
|
+
|
|
206
|
+
<collection name="events"
|
|
207
|
+
filters="{upcoming: true}"
|
|
208
|
+
data="{limit: 8}"
|
|
209
|
+
pagination="events-upcoming"
|
|
210
|
+
prevPages="3"
|
|
211
|
+
nextPages="1">
|
|
212
|
+
<div class="event">{{row.title}} - {{row.date}}</div>
|
|
213
|
+
</collection>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Pagination Attributes:**
|
|
217
|
+
- `pagination="collection-id"` - Enables pagination with unique identifier
|
|
218
|
+
- `container="templateName"` - Custom pagination template (defaults to "pagedCollection")
|
|
219
|
+
- `prevPages="2"` - Number of previous page links to show (default: 2)
|
|
220
|
+
- `nextPages="2"` - Number of next page links to show (default: 2)
|
|
221
|
+
|
|
222
|
+
**Pagination URL Parameters:**
|
|
223
|
+
Pagination state is managed via URL query parameters:
|
|
224
|
+
```
|
|
225
|
+
/articles?articles-1-key={"page":2,"limit":10,"sortBy":"created_at","sortDirection":"desc"}
|
|
226
|
+
/news?news-tech-key={"page":3,"limit":5,"category":"technology"}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Custom Pagination Template:**
|
|
230
|
+
Create `templates/partials/pagedCollection.html`:
|
|
231
|
+
```html
|
|
232
|
+
<div class="row paginated-contents">
|
|
233
|
+
<div class="collection-content col-lg-12 mt-2 mb-2">
|
|
234
|
+
{{&collectionContentForCurrentPage}}
|
|
235
|
+
</div>
|
|
236
|
+
<div class="pagination col-lg-12 mt-2 mb-2">
|
|
237
|
+
<ul class="pagination-list">
|
|
238
|
+
{{#prevPageUrl}}
|
|
239
|
+
<li><a href="{{prevPageUrl}}" class="page-link">{{&prevPageLabel}}</a></li>
|
|
240
|
+
{{/prevPageUrl}}
|
|
241
|
+
{{#prevPages}}
|
|
242
|
+
<li><a href="{{pageUrl}}" class="page-link">{{&pageLabel}}</a></li>
|
|
243
|
+
{{/prevPages}}
|
|
244
|
+
<li class="current">{{¤tPage}}</li>
|
|
245
|
+
{{#nextPages}}
|
|
246
|
+
<li><a href="{{pageUrl}}" class="page-link">{{&pageLabel}}</a></li>
|
|
247
|
+
{{/nextPages}}
|
|
248
|
+
{{#nextPageUrl}}
|
|
249
|
+
<li><a href="{{nextPageUrl}}" class="page-link">{{&nextPageLabel}}</a></li>
|
|
250
|
+
{{/nextPageUrl}}
|
|
251
|
+
</ul>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
181
254
|
```
|
|
182
255
|
|
|
256
|
+
**Available Pagination Template Variables:**
|
|
257
|
+
- `{{&collectionContentForCurrentPage}}` - Rendered collection items for current page
|
|
258
|
+
- `{{currentPage}}` - Current page number
|
|
259
|
+
- `{{totalPages}}` - Total number of pages
|
|
260
|
+
- `{{totalRecords}}` - Total number of records
|
|
261
|
+
- `{{prevPageUrl}}` / `{{nextPageUrl}}` - Previous/next page URLs
|
|
262
|
+
- `{{&prevPageLabel}}` / `{{&nextPageLabel}}` - Previous/next link labels ("Previous"/"Next")
|
|
263
|
+
- `{{#prevPages}}` / `{{#nextPages}}` - Arrays of page objects with `pageUrl` and `pageLabel`
|
|
264
|
+
- `{{hasNextPage}}` / `{{hasPrevPage}}` - Boolean flags for navigation availability
|
|
265
|
+
|
|
183
266
|
**Custom Partials with Variables:**
|
|
184
267
|
|
|
185
268
|
*New HTML-style partial tags:*
|
|
@@ -818,9 +818,10 @@
|
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
.extra-actions {
|
|
821
|
-
display:
|
|
821
|
+
display: flex;
|
|
822
822
|
width: 100%;
|
|
823
|
-
|
|
823
|
+
justify-content: end;
|
|
824
|
+
margin: 1rem 0;
|
|
824
825
|
}
|
|
825
826
|
|
|
826
827
|
.cache-clean-form {
|
|
@@ -847,4 +848,47 @@
|
|
|
847
848
|
overflow: auto;
|
|
848
849
|
}
|
|
849
850
|
|
|
851
|
+
.cache-confirm-dialog {
|
|
852
|
+
border: none;
|
|
853
|
+
border-radius: 8px;
|
|
854
|
+
padding: 0;
|
|
855
|
+
max-width: 500px;
|
|
856
|
+
width: 90%;
|
|
857
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.cache-confirm-dialog::backdrop {
|
|
861
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.cache-dialog-content {
|
|
865
|
+
padding: 1rem;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.cache-dialog-content h5 {
|
|
869
|
+
margin: 0 0 1rem 0;
|
|
870
|
+
font-size: 18px;
|
|
871
|
+
color: var(--darkGrey);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.cache-dialog-content p {
|
|
875
|
+
margin: 0 0 1rem 0;
|
|
876
|
+
color: var(--darkGrey);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.cache-warning {
|
|
880
|
+
padding: 1rem;
|
|
881
|
+
background-color: #fff3cd;
|
|
882
|
+
border: 1px solid #ffeaa7;
|
|
883
|
+
border-radius: 4px;
|
|
884
|
+
color: #856404;
|
|
885
|
+
margin-bottom: 1rem;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.cache-dialog-actions {
|
|
889
|
+
display: flex;
|
|
890
|
+
justify-content: flex-end;
|
|
891
|
+
gap: 1rem;
|
|
892
|
+
}
|
|
893
|
+
|
|
850
894
|
}
|
|
@@ -276,4 +276,28 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
// cache clear all functionality:
|
|
280
|
+
let cacheClearAllButton = document.querySelector('.cache-clear-all-button');
|
|
281
|
+
let cacheConfirmDialog = document.querySelector('.cache-confirm-dialog');
|
|
282
|
+
let cacheDialogCancel = document.querySelector('.cache-dialog-cancel');
|
|
283
|
+
let cacheClearForm = document.querySelector('.cache-clear-form');
|
|
284
|
+
if(cacheClearAllButton && cacheConfirmDialog){
|
|
285
|
+
cacheClearAllButton.addEventListener('click', () => {
|
|
286
|
+
cacheConfirmDialog.showModal();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if(cacheDialogCancel && cacheConfirmDialog){
|
|
290
|
+
cacheDialogCancel.addEventListener('click', () => {
|
|
291
|
+
cacheConfirmDialog.close();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if(cacheClearForm){
|
|
295
|
+
cacheClearForm.addEventListener('submit', (event) => {
|
|
296
|
+
let submitButton = cacheClearForm.querySelector('button[type="submit"]');
|
|
297
|
+
if(submitButton){
|
|
298
|
+
submitButton.disabled = true;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
279
303
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<button type="button" class="button button-warning cache-clear-all-button">
|
|
2
|
+
{{buttonText}}
|
|
3
|
+
</button>
|
|
4
|
+
<dialog class="cache-confirm-dialog">
|
|
5
|
+
<div class="cache-dialog-content">
|
|
6
|
+
<h5>{{confirmTitle}}</h5>
|
|
7
|
+
<p>{{confirmMessage}}</p>
|
|
8
|
+
<div class="alert cache-warning">
|
|
9
|
+
<strong>{{warningText}}</strong> {{warningMessage}}
|
|
10
|
+
</div>
|
|
11
|
+
<div class="cache-dialog-actions">
|
|
12
|
+
<button type="button" class="button button-secondary cache-dialog-cancel">{{cancelText}}</button>
|
|
13
|
+
<form method="post" action="{{clearAllCacheRoute}}" class="cache-clear-form">
|
|
14
|
+
<button type="submit" class="button button-danger">{{confirmText}}</button>
|
|
15
|
+
</form>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</dialog>
|
|
@@ -37,7 +37,10 @@ class ContentsBuilder
|
|
|
37
37
|
this.adminContents.layout = await this.buildLayout();
|
|
38
38
|
this.adminContents.sideBar = await this.buildSideBar();
|
|
39
39
|
this.adminContents.login = await this.renderRoute(this.adminFilesContents.login, '');
|
|
40
|
-
this.adminContents.dashboard = await this.renderRoute(
|
|
40
|
+
this.adminContents.dashboard = await this.renderRoute(
|
|
41
|
+
this.adminFilesContents.dashboard,
|
|
42
|
+
this.adminContents.sideBar
|
|
43
|
+
);
|
|
41
44
|
this.adminContents.entities = await this.buildEntitiesContents();
|
|
42
45
|
await this.emitEvent('reldens.buildAdminContentsAfter');
|
|
43
46
|
return this.adminContents;
|
|
@@ -67,7 +70,8 @@ class ContentsBuilder
|
|
|
67
70
|
navigationContents = eventBuildSideBarBefore.navigationContents;
|
|
68
71
|
for(let driverResource of this.resources()){
|
|
69
72
|
let navigation = driverResource.options?.navigation;
|
|
70
|
-
let name = this.translations.labels[driverResource.id()]
|
|
73
|
+
let name = this.translations.labels[driverResource.id()]
|
|
74
|
+
|| this.translations.labels[driverResource.entityKey];
|
|
71
75
|
let path = this.rootPath+'/'+(driverResource.id().replace(/_/g, '-'));
|
|
72
76
|
if(navigation?.name){
|
|
73
77
|
if(!navigationContents[navigation.name]){
|
|
@@ -93,10 +97,7 @@ class ContentsBuilder
|
|
|
93
97
|
for(let subId of Object.keys(navigationContents[id])){
|
|
94
98
|
subItems += navigationContents[id][subId];
|
|
95
99
|
}
|
|
96
|
-
navigationView += await this.render(
|
|
97
|
-
this.adminFilesContents.sideBarHeader,
|
|
98
|
-
{name: id, subItems}
|
|
99
|
-
);
|
|
100
|
+
navigationView += await this.render(this.adminFilesContents.sideBarHeader, {name: id, subItems});
|
|
100
101
|
continue;
|
|
101
102
|
}
|
|
102
103
|
navigationView += navigationContents[id];
|
|
@@ -51,6 +51,14 @@ class RouterContents
|
|
|
51
51
|
}
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
|
+
let listProperties = {
|
|
55
|
+
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath
|
|
56
|
+
};
|
|
57
|
+
await this.emitEvent('reldens.adminListPropertiesPopulation', {
|
|
58
|
+
req,
|
|
59
|
+
driverResource,
|
|
60
|
+
listProperties
|
|
61
|
+
});
|
|
54
62
|
return await this.adminContentsRenderRoute(
|
|
55
63
|
await this.adminContentsRender(
|
|
56
64
|
this.adminContentsEntities()[entityPath].list,
|
|
@@ -67,7 +75,8 @@ class RouterContents
|
|
|
67
75
|
}),
|
|
68
76
|
rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, filters)
|
|
69
77
|
}),
|
|
70
|
-
pagination: renderedPagination
|
|
78
|
+
pagination: renderedPagination,
|
|
79
|
+
extraContentForList: sc.get(listProperties, 'extraContentForList', '')
|
|
71
80
|
}, ...driverResource.options.filterProperties.map((property) => {
|
|
72
81
|
let filterValue = (filtersFromParams[property] || '');
|
|
73
82
|
return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
|
|
@@ -274,13 +283,21 @@ class RouterContents
|
|
|
274
283
|
);
|
|
275
284
|
fields.push({
|
|
276
285
|
name: property,
|
|
277
|
-
value: await this.generatePropertyRenderedValue(
|
|
278
|
-
|
|
286
|
+
value: await this.generatePropertyRenderedValue(
|
|
287
|
+
fieldValue,
|
|
288
|
+
fieldName,
|
|
289
|
+
resourceProperties[property]
|
|
290
|
+
),
|
|
291
|
+
viewLink: '' !== idProperty
|
|
292
|
+
? this.generateEntityRoute('viewPath', driverResource, idProperty, entity)
|
|
293
|
+
: ''
|
|
279
294
|
});
|
|
280
295
|
}
|
|
281
296
|
entityRows.push({
|
|
282
297
|
fields,
|
|
283
|
-
editLink: '' !== idProperty
|
|
298
|
+
editLink: '' !== idProperty
|
|
299
|
+
? this.generateEntityRoute('editPath', driverResource, idProperty, entity)
|
|
300
|
+
: '',
|
|
284
301
|
deleteLink: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
285
302
|
id: entity[idProperty]
|
|
286
303
|
});
|
|
@@ -317,12 +334,13 @@ class RouterContents
|
|
|
317
334
|
continue;
|
|
318
335
|
}
|
|
319
336
|
for(let entity of entities){
|
|
337
|
+
let bucket = sc.get(property, 'bucket', '');
|
|
320
338
|
if(!property.isArray){
|
|
321
|
-
FileHandler.remove([
|
|
339
|
+
FileHandler.remove([bucket, entity[propertyKey]]);
|
|
322
340
|
continue;
|
|
323
341
|
}
|
|
324
342
|
for(let entityFile of entity[propertyKey].split(property.isArray)){
|
|
325
|
-
FileHandler.remove([
|
|
343
|
+
FileHandler.remove([bucket, entityFile]);
|
|
326
344
|
}
|
|
327
345
|
}
|
|
328
346
|
}
|
|
@@ -337,7 +355,10 @@ class RouterContents
|
|
|
337
355
|
}
|
|
338
356
|
let propertyUpdateValue = sc.get(req.body, i, null);
|
|
339
357
|
let property = resourceProperties[i];
|
|
340
|
-
|
|
358
|
+
if('null' === propertyUpdateValue){
|
|
359
|
+
propertyUpdateValue = null;
|
|
360
|
+
}
|
|
361
|
+
let propertyType = sc.get(property, 'type', 'string');
|
|
341
362
|
if(property.isUpload){
|
|
342
363
|
propertyType = 'upload';
|
|
343
364
|
propertyUpdateValue = this.prepareUploadPatchData(req, i, propertyUpdateValue, property);
|
|
@@ -345,21 +366,23 @@ class RouterContents
|
|
|
345
366
|
if('boolean' === propertyType){
|
|
346
367
|
propertyUpdateValue = '1' === propertyUpdateValue || 'on' === propertyUpdateValue;
|
|
347
368
|
}
|
|
348
|
-
|
|
369
|
+
let isEmpty = '' === propertyUpdateValue;
|
|
370
|
+
let isNull = null === propertyUpdateValue;
|
|
371
|
+
if('number' === propertyType && !isNull && !isEmpty){
|
|
349
372
|
propertyUpdateValue = Number(propertyUpdateValue);
|
|
350
373
|
}
|
|
351
|
-
if('string' === propertyType &&
|
|
374
|
+
if('string' === propertyType && !isNull && !isEmpty){
|
|
352
375
|
propertyUpdateValue = String(propertyUpdateValue);
|
|
353
376
|
}
|
|
354
|
-
if(
|
|
377
|
+
if(isEmpty && !property.isRequired){
|
|
355
378
|
propertyUpdateValue = null;
|
|
356
379
|
}
|
|
357
380
|
let isUploadCreate = property.isUpload && !id;
|
|
358
|
-
if(property.isRequired && (
|
|
381
|
+
if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
|
|
359
382
|
Logger.critical('Bad patch data on update.', propertyUpdateValue, property);
|
|
360
383
|
return false;
|
|
361
384
|
}
|
|
362
|
-
if(!property.isUpload || (property.isUpload &&
|
|
385
|
+
if(!property.isUpload || (property.isUpload && !isNull)){
|
|
363
386
|
entityDataPatch[i] = propertyUpdateValue;
|
|
364
387
|
}
|
|
365
388
|
}
|
|
@@ -405,9 +428,14 @@ class RouterContents
|
|
|
405
428
|
generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
|
|
406
429
|
{
|
|
407
430
|
let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
|
|
431
|
+
if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
|
|
432
|
+
fieldValue = sc.toJsonString(fieldValue);
|
|
433
|
+
}
|
|
408
434
|
let fieldName = propertyKey;
|
|
409
435
|
if('boolean' === resourceProperty.type){
|
|
410
|
-
fieldValue = 1 === entity[propertyKey]
|
|
436
|
+
fieldValue = 1 === entity[propertyKey]
|
|
437
|
+
|| '1' === entity[propertyKey]
|
|
438
|
+
|| true === entity[propertyKey] ? 'Yes' : 'No';
|
|
411
439
|
}
|
|
412
440
|
if('datetime' === resourceProperty.type){
|
|
413
441
|
fieldValue = '' !== fieldValue ? sc.formatDate(new Date(fieldValue)) : '';
|
|
@@ -440,8 +468,16 @@ class RouterContents
|
|
|
440
468
|
{
|
|
441
469
|
let entityPropertyValue = sc.get(entity, propertyKey, null);
|
|
442
470
|
let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '');
|
|
471
|
+
if('null' === fieldValue){
|
|
472
|
+
fieldValue = '';
|
|
473
|
+
}
|
|
474
|
+
if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
|
|
475
|
+
fieldValue = sc.toJsonString(fieldValue);
|
|
476
|
+
}
|
|
443
477
|
if('boolean' === resourceProperty.type){
|
|
444
|
-
fieldValue = 1 === entityPropertyValue
|
|
478
|
+
fieldValue = 1 === entityPropertyValue
|
|
479
|
+
|| '1' === entityPropertyValue
|
|
480
|
+
|| true === entityPropertyValue ? ' checked="checked"' : '';
|
|
445
481
|
}
|
|
446
482
|
if('datetime' === resourceProperty.type){
|
|
447
483
|
fieldValue = !entityPropertyValue || '' === entityPropertyValue
|
|
@@ -452,7 +488,9 @@ class RouterContents
|
|
|
452
488
|
let relationDriverResource = this.resourcesByReference()[resourceProperty.reference];
|
|
453
489
|
let relation = this.relations()[resourceProperty.reference];
|
|
454
490
|
let relationKey = resourceProperty.alias || resourceProperty.reference;
|
|
455
|
-
let relationTitleProperty = relation
|
|
491
|
+
let relationTitleProperty = relation
|
|
492
|
+
? relation[relationKey]
|
|
493
|
+
: this.fetchEntityIdPropertyKey(relationDriverResource);
|
|
456
494
|
let options = (await this.fetchRelationOptions(relationDriverResource)).map((option) => {
|
|
457
495
|
let value = option[this.fetchEntityIdPropertyKey(relationDriverResource)];
|
|
458
496
|
return {
|
|
@@ -462,10 +500,11 @@ class RouterContents
|
|
|
462
500
|
};
|
|
463
501
|
});
|
|
464
502
|
if(!resourceProperty.isRequired){
|
|
503
|
+
let isSelected = !entity || null === entity[propertyKey] || '' === entity[propertyKey];
|
|
465
504
|
options.unshift({
|
|
466
505
|
label: '-- Select --',
|
|
467
506
|
value: '',
|
|
468
|
-
selected:
|
|
507
|
+
selected: isSelected ? ' selected="selected"' : ''
|
|
469
508
|
});
|
|
470
509
|
}
|
|
471
510
|
return options;
|
|
@@ -476,6 +515,9 @@ class RouterContents
|
|
|
476
515
|
async fetchRelationOptions(relationDriverResource)
|
|
477
516
|
{
|
|
478
517
|
let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
|
|
518
|
+
if(!relationEntityRepository){
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
479
521
|
return await relationEntityRepository.loadAll();
|
|
480
522
|
}
|
|
481
523
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Reldens - AdminManagerValidator
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
+
|
|
6
7
|
const { ValidatorInterface, Logger } = require('@reldens/utils');
|
|
7
8
|
|
|
8
9
|
class AdminManagerValidator extends ValidatorInterface
|
|
@@ -29,7 +30,7 @@ class AdminManagerValidator extends ValidatorInterface
|
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
return true
|
|
33
|
+
return true;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
}
|
package/lib/admin-manager.js
CHANGED
|
@@ -115,7 +115,7 @@ class AdminManager
|
|
|
115
115
|
viewPath: this.viewPath,
|
|
116
116
|
editPath: this.editPath,
|
|
117
117
|
deletePath: this.deletePath,
|
|
118
|
-
emitEvent:
|
|
118
|
+
emitEvent: this.emitEvent,
|
|
119
119
|
adminContentsRender: (...args) => this.contentsBuilder.render(...args),
|
|
120
120
|
adminContentsRenderRoute: (...args) => this.contentsBuilder.renderRoute(...args),
|
|
121
121
|
adminContentsEntities: () => this.contentsBuilder.adminContents.entities,
|
|
@@ -135,8 +135,10 @@ class AdminManager
|
|
|
135
135
|
cacheManager: this.cacheManager,
|
|
136
136
|
renderCallback: this.renderCallback,
|
|
137
137
|
cacheCleanButton: this.adminFilesContents.cacheCleanButton,
|
|
138
|
+
clearAllCacheButton: this.adminFilesContents.clearAllCacheButton,
|
|
138
139
|
translations: this.translations,
|
|
139
|
-
cacheCleanRoute: this.cacheRoutesHandler.cacheCleanRoute
|
|
140
|
+
cacheCleanRoute: this.cacheRoutesHandler.cacheCleanRoute,
|
|
141
|
+
clearAllCacheRoute: this.cacheRoutesHandler.clearAllCacheRoute
|
|
140
142
|
});
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -21,7 +21,9 @@ class AdminTranslations
|
|
|
21
21
|
reldensGithubText: 'Need a new feature?'
|
|
22
22
|
+' Would you like to contribute with code?'
|
|
23
23
|
+' Find the source code or create an issue in GitHub',
|
|
24
|
-
reldensLoading: 'Loading...'
|
|
24
|
+
reldensLoading: 'Loading...',
|
|
25
|
+
confirmClearCacheMessage: 'Are you sure you want to clear all cache? This action cannot be undone.',
|
|
26
|
+
clearCacheWarning: 'This will clear all cached routes and may impact site performance temporarily.'
|
|
25
27
|
},
|
|
26
28
|
labels: {
|
|
27
29
|
navigation: 'Reldens - CMS',
|
|
@@ -32,6 +34,12 @@ class AdminTranslations
|
|
|
32
34
|
shuttingDown: 'Server is shutting down in:',
|
|
33
35
|
submitShutdownLabel: 'Shutdown Server',
|
|
34
36
|
submitCancelLabel: 'Cancel Server Shutdown',
|
|
37
|
+
cleanCache: 'Clean Cache',
|
|
38
|
+
clearAllCache: 'Clear All Cache',
|
|
39
|
+
confirmClearCache: 'Confirm Clear Cache',
|
|
40
|
+
warning: 'Warning:',
|
|
41
|
+
cancel: 'Cancel',
|
|
42
|
+
confirm: 'Continue',
|
|
35
43
|
}
|
|
36
44
|
};
|
|
37
45
|
for(let i of Object.keys(translations)){
|
|
@@ -15,8 +15,10 @@ class AddCacheButtonSubscriber
|
|
|
15
15
|
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
16
16
|
this.renderCallback = sc.get(props, 'renderCallback', false);
|
|
17
17
|
this.cacheCleanButton = sc.get(props, 'cacheCleanButton', '');
|
|
18
|
+
this.clearAllCacheButton = sc.get(props, 'clearAllCacheButton', '');
|
|
18
19
|
this.translations = sc.get(props, 'translations', {});
|
|
19
20
|
this.cacheCleanRoute = sc.get(props, 'cacheCleanRoute', '');
|
|
21
|
+
this.clearAllCacheRoute = sc.get(props, 'clearAllCacheRoute', '');
|
|
20
22
|
this.setupEvents();
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -37,14 +39,14 @@ class AddCacheButtonSubscriber
|
|
|
37
39
|
//Logger.debug('Listening events PropertiesPopulation.');
|
|
38
40
|
this.events.on('reldens.adminViewPropertiesPopulation', this.populateViewFields.bind(this));
|
|
39
41
|
this.events.on('reldens.adminEditPropertiesPopulation', this.populateEditFields.bind(this));
|
|
40
|
-
|
|
42
|
+
this.events.on('reldens.adminListPropertiesPopulation', this.populateListFields.bind(this));
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
async populateViewFields(event)
|
|
44
46
|
{
|
|
45
47
|
let cacheButton = await this.generateCacheCleanButton(event);
|
|
46
48
|
if(!cacheButton){
|
|
47
|
-
Logger.
|
|
49
|
+
//Logger.info('Missing cache button contents on AddCacheButtonSubscriber.');
|
|
48
50
|
return false;
|
|
49
51
|
}
|
|
50
52
|
if(!event.renderedViewProperties.extraContentForView){
|
|
@@ -59,7 +61,7 @@ class AddCacheButtonSubscriber
|
|
|
59
61
|
{
|
|
60
62
|
let cacheButton = await this.generateCacheCleanButton(event);
|
|
61
63
|
if(!cacheButton){
|
|
62
|
-
Logger.
|
|
64
|
+
//Logger.info('Missing cache button contents on AddCacheButtonSubscriber.');
|
|
63
65
|
return false;
|
|
64
66
|
}
|
|
65
67
|
if(!event.renderedEditProperties.extraContentForEdit){
|
|
@@ -70,12 +72,30 @@ class AddCacheButtonSubscriber
|
|
|
70
72
|
return true;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
async populateListFields(event)
|
|
76
|
+
{
|
|
77
|
+
let clearAllButton = await this.generateClearAllCacheButton(event);
|
|
78
|
+
if(!clearAllButton){
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if(!event.listProperties.extraContentForList){
|
|
82
|
+
event.listProperties.extraContentForList = '';
|
|
83
|
+
}
|
|
84
|
+
event.listProperties.extraContentForList += clearAllButton;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
async generateCacheCleanButton(event)
|
|
74
89
|
{
|
|
75
90
|
if('routes' !== event.driverResource.id()){
|
|
76
91
|
return false;
|
|
77
92
|
}
|
|
78
|
-
if(!event.loadedEntity
|
|
93
|
+
if(!event.loadedEntity){
|
|
94
|
+
Logger.error('Missing loaded entity on AddCacheButtonSubscriber.');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if(!event.loadedEntity.id){
|
|
98
|
+
Logger.error('Missing loaded entity ID on AddCacheButtonSubscriber.');
|
|
79
99
|
return false;
|
|
80
100
|
}
|
|
81
101
|
if(!this.cacheCleanButton){
|
|
@@ -91,7 +111,35 @@ class AddCacheButtonSubscriber
|
|
|
91
111
|
{
|
|
92
112
|
cacheCleanRoute: this.cacheCleanRoute,
|
|
93
113
|
routeId: event.loadedEntity.id,
|
|
94
|
-
buttonText: sc.get(this.translations, 'cleanCache', '
|
|
114
|
+
buttonText: sc.get(this.translations.labels, 'cleanCache', 'cleanCache')
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async generateClearAllCacheButton(event)
|
|
120
|
+
{
|
|
121
|
+
if('routes' !== event.driverResource.id()){
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if(!this.clearAllCacheButton){
|
|
125
|
+
Logger.error('Clear all cache button template content not found');
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
if(!this.renderCallback){
|
|
129
|
+
Logger.error('Render callback not available for clear all cache button');
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
return await this.renderCallback(
|
|
133
|
+
this.clearAllCacheButton,
|
|
134
|
+
{
|
|
135
|
+
buttonText: sc.get(this.translations.labels, 'clearAllCache', 'clearAllCache'),
|
|
136
|
+
clearAllCacheRoute: this.clearAllCacheRoute,
|
|
137
|
+
confirmTitle: sc.get(this.translations.labels, 'confirmClearCache', 'confirmClearCache'),
|
|
138
|
+
confirmMessage: sc.get(this.translations.messages, 'confirmClearCacheMessage', 'confirmClearCacheMessage'),
|
|
139
|
+
warningText: sc.get(this.translations.labels, 'warning', 'warning'),
|
|
140
|
+
warningMessage: sc.get(this.translations.messages, 'clearCacheWarning', 'clearCacheWarning'),
|
|
141
|
+
cancelText: sc.get(this.translations.labels, 'cancel', 'cancel'),
|
|
142
|
+
confirmText: sc.get(this.translations.labels, 'confirm', 'confirm')
|
|
95
143
|
}
|
|
96
144
|
);
|
|
97
145
|
}
|