@reldens/cms 0.49.0 → 0.50.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/.claude/advanced-usage-guide.md +275 -0
- package/CLAUDE.md +67 -0
- package/admin/reldens-admin-client.css +19 -11
- package/admin/reldens-admin-client.js +34 -0
- package/admin/templates/cache-clean-button.html +1 -0
- package/admin/templates/edit.html +3 -0
- package/admin/templates/fields/edit/file.html +1 -1
- package/admin/templates/view.html +5 -2
- package/lib/admin-manager/router-contents.js +11 -4
- package/lib/cache/add-cache-button-subscriber.js +17 -3
- package/lib/cache/cache-manager.js +36 -18
- package/lib/cache/cache-routes-handler.js +4 -1
- package/lib/entities-config-processor.js +59 -0
- package/lib/manager.js +13 -2
- package/lib/template-reloader.js +10 -10
- package/package.json +1 -1
|
@@ -96,3 +96,278 @@ cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
|
|
|
96
96
|
entitySerializedData.customField = 'Custom Value';
|
|
97
97
|
});
|
|
98
98
|
```
|
|
99
|
+
|
|
100
|
+
## Event-Driven Customizations
|
|
101
|
+
|
|
102
|
+
The CMS uses an event system for extensibility. This allows you to add custom behavior without modifying core CMS files.
|
|
103
|
+
|
|
104
|
+
### Admin UI Customizations
|
|
105
|
+
|
|
106
|
+
Add buttons and content to admin views using events. Always use templates and renderCallback - never hardcode HTML in classes.
|
|
107
|
+
|
|
108
|
+
**Step 1: Create template file** (`admin/templates/custom-button.html`):
|
|
109
|
+
```html
|
|
110
|
+
<button class="button button-primary" type="submit" form="edit-form"
|
|
111
|
+
name="customAction" value="customValue">
|
|
112
|
+
{{&buttonText}}
|
|
113
|
+
</button>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Step 2: Register template** in `lib/templates-list.js`:
|
|
117
|
+
```javascript
|
|
118
|
+
module.exports.TemplatesList = {
|
|
119
|
+
// ... existing templates
|
|
120
|
+
customButton: 'custom-button.html',
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Step 3: Create subscriber class**:
|
|
125
|
+
```javascript
|
|
126
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
127
|
+
|
|
128
|
+
class CustomButtonSubscriber
|
|
129
|
+
{
|
|
130
|
+
|
|
131
|
+
constructor(props)
|
|
132
|
+
{
|
|
133
|
+
this.events = sc.get(props, 'events', false);
|
|
134
|
+
this.renderCallback = sc.get(props, 'renderCallback', false);
|
|
135
|
+
this.customButtonTemplate = sc.get(props, 'customButtonTemplate', '');
|
|
136
|
+
this.translations = sc.get(props, 'translations', {});
|
|
137
|
+
this.setupEvents();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setupEvents()
|
|
141
|
+
{
|
|
142
|
+
if(!this.events){
|
|
143
|
+
Logger.error('Events Manager not found on CustomButtonSubscriber.');
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
this.events.on('reldens.adminEditPropertiesPopulation', this.addCustomButton.bind(this));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async addCustomButton(event)
|
|
150
|
+
{
|
|
151
|
+
if(!this.customButtonTemplate){
|
|
152
|
+
Logger.error('Custom button template not found');
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
if(!this.renderCallback){
|
|
156
|
+
Logger.error('Render callback not available');
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
if(!event.renderedEditProperties.extraContentForEdit){
|
|
160
|
+
event.renderedEditProperties.extraContentForEdit = '';
|
|
161
|
+
}
|
|
162
|
+
let buttonHtml = await this.renderCallback(
|
|
163
|
+
this.customButtonTemplate,
|
|
164
|
+
{
|
|
165
|
+
buttonText: sc.get(this.translations.labels, 'customAction', 'Custom Action')
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
event.renderedEditProperties.extraContentForEdit += buttonHtml;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Step 4: Initialize in AdminManager**:
|
|
175
|
+
```javascript
|
|
176
|
+
this.customButtonSubscriber = new CustomButtonSubscriber({
|
|
177
|
+
events: this.events,
|
|
178
|
+
renderCallback: this.renderCallback,
|
|
179
|
+
customButtonTemplate: this.adminFilesContents.customButton,
|
|
180
|
+
translations: this.translations
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**See `lib/cache/save-and-clear-cache-subscriber.js` for complete working example.**
|
|
185
|
+
|
|
186
|
+
### Form Processing Customizations
|
|
187
|
+
|
|
188
|
+
Handle custom save actions and form submissions:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
class CustomFormHandler
|
|
192
|
+
{
|
|
193
|
+
|
|
194
|
+
constructor(props)
|
|
195
|
+
{
|
|
196
|
+
this.events = props.events;
|
|
197
|
+
this.dataServer = props.dataServer;
|
|
198
|
+
this.setupEvents();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setupEvents()
|
|
202
|
+
{
|
|
203
|
+
this.events.on('reldens.dynamicFormRequestHandler.beforeSave', this.beforeSave.bind(this));
|
|
204
|
+
this.events.on('reldens.dynamicFormRequestHandler.afterSave', this.afterSave.bind(this));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async beforeSave(event)
|
|
208
|
+
{
|
|
209
|
+
if(event.validationResult.errors){
|
|
210
|
+
event.validationResult.extraErrors = this.customValidation(event.formData);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async afterSave(event)
|
|
215
|
+
{
|
|
216
|
+
let customAction = event.req?.body?.customAction;
|
|
217
|
+
if('customValue' === customAction){
|
|
218
|
+
await this.performCustomAction(event.result);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
customValidation(formData)
|
|
223
|
+
{
|
|
224
|
+
let errors = [];
|
|
225
|
+
if(formData.customField && formData.customField.length < 10){
|
|
226
|
+
errors.push('customField must be at least 10 characters');
|
|
227
|
+
}
|
|
228
|
+
return errors;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async performCustomAction(savedEntity)
|
|
232
|
+
{
|
|
233
|
+
Logger.info('Performing custom action for entity:', savedEntity.id);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let manager = new Manager(managerConfiguration);
|
|
239
|
+
new CustomFormHandler({
|
|
240
|
+
events: manager.events,
|
|
241
|
+
dataServer: manager.dataServer
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Entity Config Overrides
|
|
246
|
+
|
|
247
|
+
Customize generated entity configurations without editing generated files:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
class CmsPagesOverride
|
|
251
|
+
{
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {Object} baseConfig
|
|
255
|
+
* @param {Object} props
|
|
256
|
+
* @returns {Object}
|
|
257
|
+
*/
|
|
258
|
+
static propertiesConfig(baseConfig, props)
|
|
259
|
+
{
|
|
260
|
+
baseConfig.properties.meta_og_image = {
|
|
261
|
+
...baseConfig.properties.meta_og_image,
|
|
262
|
+
isUpload: true,
|
|
263
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
264
|
+
bucket: 'cms-og-images',
|
|
265
|
+
bucketPath: '/og-images/'
|
|
266
|
+
};
|
|
267
|
+
baseConfig.properties.status = {
|
|
268
|
+
...baseConfig.properties.status,
|
|
269
|
+
type: 'select',
|
|
270
|
+
options: [
|
|
271
|
+
{ value: 'draft', label: 'Draft' },
|
|
272
|
+
{ value: 'published', label: 'Published' },
|
|
273
|
+
{ value: 'archived', label: 'Archived' }
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
return baseConfig;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let manager = new Manager({
|
|
282
|
+
...managerConfiguration,
|
|
283
|
+
entitiesConfigOverride: {
|
|
284
|
+
'cmsPages': CmsPagesOverride // CRITICAL: Use camelCase key from entities-config.js
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**CRITICAL: Entity keys must match exactly as defined in `generated-entities/entities-config.js`:**
|
|
290
|
+
- Use `'cmsPages'` NOT `'cms-pages'`
|
|
291
|
+
- Use `'cmsBlocks'` NOT `'cms-blocks'`
|
|
292
|
+
- Use `'cmsForms'` NOT `'cms-forms'`
|
|
293
|
+
- Keys are camelCase, not kebab-case
|
|
294
|
+
|
|
295
|
+
### Conditional Event Handling
|
|
296
|
+
|
|
297
|
+
Control when customizations apply based on conditions:
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
301
|
+
|
|
302
|
+
class ConditionalCustomization
|
|
303
|
+
{
|
|
304
|
+
|
|
305
|
+
constructor(props)
|
|
306
|
+
{
|
|
307
|
+
this.events = sc.get(props, 'events', false);
|
|
308
|
+
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
309
|
+
this.renderCallback = sc.get(props, 'renderCallback', false);
|
|
310
|
+
this.contentTemplate = sc.get(props, 'contentTemplate', '');
|
|
311
|
+
this.setupEvents();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
setupEvents()
|
|
315
|
+
{
|
|
316
|
+
if(!this.cacheManager?.isEnabled()){
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if(!this.events){
|
|
320
|
+
Logger.error('Events Manager not found.');
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
this.events.on('reldens.adminEditPropertiesPopulation', this.addContent.bind(this));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async addContent(event)
|
|
327
|
+
{
|
|
328
|
+
if('routes' !== event.driverResource.id()){
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
if(!this.contentTemplate || !this.renderCallback){
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if(!event.renderedEditProperties.extraContentForEdit){
|
|
335
|
+
event.renderedEditProperties.extraContentForEdit = '';
|
|
336
|
+
}
|
|
337
|
+
let contentHtml = await this.renderCallback(this.contentTemplate, {});
|
|
338
|
+
event.renderedEditProperties.extraContentForEdit += contentHtml;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let manager = new Manager(managerConfiguration);
|
|
344
|
+
new ConditionalCustomization({
|
|
345
|
+
events: manager.events,
|
|
346
|
+
cacheManager: manager.cacheManager,
|
|
347
|
+
renderCallback: manager.renderCallback,
|
|
348
|
+
contentTemplate: manager.adminFilesContents.conditionalContent
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**CRITICAL: Never hardcode HTML strings in subscriber classes. Always use templates with renderCallback and translations.**
|
|
353
|
+
|
|
354
|
+
### Available Admin Events
|
|
355
|
+
|
|
356
|
+
**View/Edit/List Property Population**:
|
|
357
|
+
- `reldens.adminViewPropertiesPopulation` - Add content to view pages
|
|
358
|
+
- `reldens.adminEditPropertiesPopulation` - Add content to edit pages
|
|
359
|
+
- `reldens.adminListPropertiesPopulation` - Add content to list pages
|
|
360
|
+
|
|
361
|
+
**Entity Operations**:
|
|
362
|
+
- `reldens.adminBeforeEntitySave` - Before entity save
|
|
363
|
+
- `reldens.adminAfterEntitySave` - After entity save
|
|
364
|
+
|
|
365
|
+
**Form Processing**:
|
|
366
|
+
- `reldens.dynamicFormRequestHandler.beforeValidation` - Before form validation
|
|
367
|
+
- `reldens.dynamicFormRequestHandler.beforeSave` - Before form save
|
|
368
|
+
- `reldens.dynamicFormRequestHandler.afterSave` - After form save
|
|
369
|
+
|
|
370
|
+
**Setup Events**:
|
|
371
|
+
- `reldens.setupAdminRouter` - Setup admin router
|
|
372
|
+
- `reldens.setupAdminRoutes` - Setup admin routes
|
|
373
|
+
- `reldens.setupAdminManagers` - Setup admin managers
|
package/CLAUDE.md
CHANGED
|
@@ -521,10 +521,16 @@ The CMS provides extensive event hooks for customization:
|
|
|
521
521
|
- `reldens.setupAdminRoutes` - After route setup
|
|
522
522
|
- `reldens.setupAdminManagers` - After manager setup
|
|
523
523
|
- `reldens.adminBeforeEntitySave` - Before entity save (used by password encryption handler)
|
|
524
|
+
- `reldens.adminAfterEntitySave` - After entity save
|
|
525
|
+
- `reldens.adminViewPropertiesPopulation` - Add content to view pages
|
|
526
|
+
- `reldens.adminEditPropertiesPopulation` - Add content to edit pages
|
|
527
|
+
- `reldens.adminListPropertiesPopulation` - Add content to list pages
|
|
524
528
|
|
|
525
529
|
### Template Reloading Events
|
|
526
530
|
- `reldens.templateReloader.templatesChanged` - Templates changed
|
|
527
531
|
|
|
532
|
+
See `.claude/advanced-usage-guide.md` for comprehensive event-driven customization patterns and examples.
|
|
533
|
+
|
|
528
534
|
## Development Workflow
|
|
529
535
|
|
|
530
536
|
### Template Reloading
|
|
@@ -569,6 +575,67 @@ Define custom entity configurations in `entitiesConfig`:
|
|
|
569
575
|
}
|
|
570
576
|
```
|
|
571
577
|
|
|
578
|
+
### Entity Config Overrides
|
|
579
|
+
Override generated entity configurations without editing generated files using `entitiesConfigOverride`:
|
|
580
|
+
```javascript
|
|
581
|
+
class CmsPagesOverride
|
|
582
|
+
{
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @param {Object} baseConfig
|
|
586
|
+
* @param {Object} props
|
|
587
|
+
* @returns {Object}
|
|
588
|
+
*/
|
|
589
|
+
static propertiesConfig(baseConfig, props)
|
|
590
|
+
{
|
|
591
|
+
baseConfig.properties.meta_og_image = {
|
|
592
|
+
dbType: 'varchar',
|
|
593
|
+
isUpload: true,
|
|
594
|
+
allowedTypes: 'image',
|
|
595
|
+
bucket: 'public/assets/media',
|
|
596
|
+
bucketPath: '/assets/media/'
|
|
597
|
+
};
|
|
598
|
+
return baseConfig;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const manager = new Manager({
|
|
604
|
+
entitiesConfigOverride: {
|
|
605
|
+
'cmsPages': CmsPagesOverride // Use camelCase key from entities-config.js
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**CRITICAL: Entity keys must match exactly as defined in `generated-entities/entities-config.js`**
|
|
611
|
+
- Use camelCase: `'cmsPages'`, `'cmsBlocks'`, `'cmsForms'`
|
|
612
|
+
- NOT kebab-case: `'cms-pages'`, `'cms-blocks'`, `'cms-forms'`
|
|
613
|
+
|
|
614
|
+
Supports three override patterns:
|
|
615
|
+
- **Class-based**: Class with static `propertiesConfig(baseConfig, props)` method
|
|
616
|
+
- **Function-based**: Function that receives `(baseConfig, props)` and returns modified config
|
|
617
|
+
- **Object-based**: Plain object that gets deep merged with base config
|
|
618
|
+
|
|
619
|
+
**Upload Field Configuration:**
|
|
620
|
+
- `allowedTypes`: Must be a STRING ('image', 'audio', 'text'), NOT an array
|
|
621
|
+
- `bucket`: Relative path from projectRoot (e.g., 'public/assets/media')
|
|
622
|
+
- `bucketPath`: URL path for display (e.g., '/assets/media/')
|
|
623
|
+
- Do NOT use spread syntax - replace entire property object
|
|
624
|
+
- Do NOT use FileHandler.joinPaths() - bucket paths are relative to projectRoot
|
|
625
|
+
|
|
626
|
+
**Upload Field Removal:**
|
|
627
|
+
- Admin edit pages automatically show an "X" button before uploaded filenames
|
|
628
|
+
- Clicking X creates hidden input `clear_fieldname=1` in form
|
|
629
|
+
- Server detects this and sets field to null in database
|
|
630
|
+
- File remains on disk - only database field is cleared
|
|
631
|
+
- Template: `admin/templates/fields/edit/file-claude.html`
|
|
632
|
+
- Client JS: `admin/reldens-admin-client-claude.js` (remove upload functionality)
|
|
633
|
+
- Server handling: `lib/admin-manager/router-contents-claude.js` (clear_ parameter detection)
|
|
634
|
+
|
|
635
|
+
**Example subscribers**: See `lib/cache/add-cache-button-subscriber.js` and `lib/cache/save-and-clear-cache-subscriber.js` for complete working examples of event-driven UI customizations.
|
|
636
|
+
|
|
637
|
+
See `.claude/advanced-usage-guide.md` for detailed examples.
|
|
638
|
+
|
|
572
639
|
## Security Features
|
|
573
640
|
|
|
574
641
|
- **Authentication** - Role-based admin access
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
background-color: var(--lightGrey);
|
|
26
26
|
margin: 0;
|
|
27
27
|
padding: 0;
|
|
28
|
-
font-size:
|
|
28
|
+
font-size: 0.75rem;
|
|
29
29
|
|
|
30
30
|
.wrapper {
|
|
31
31
|
display: flex;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
right: 0;
|
|
41
41
|
padding: 1rem 5rem 1rem 2rem;
|
|
42
42
|
border-radius: 8px 0 0 8px;
|
|
43
|
-
font-size:
|
|
43
|
+
font-size: 0.875rem;
|
|
44
44
|
|
|
45
45
|
&.success, &.error {
|
|
46
46
|
display: block;
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
padding: 0.5rem 1rem;
|
|
92
92
|
border: none;
|
|
93
93
|
border-radius: 4px;
|
|
94
|
-
font-size:
|
|
94
|
+
font-size: 0.875rem;
|
|
95
95
|
cursor: pointer;
|
|
96
96
|
text-decoration: none;
|
|
97
97
|
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
color: var(--lightGrey);
|
|
212
212
|
text-decoration: none;
|
|
213
213
|
border-left: 3px solid transparent;
|
|
214
|
-
font-size:
|
|
214
|
+
font-size: 0.75rem;
|
|
215
215
|
border-left: 3px solid #0000;
|
|
216
216
|
padding: 0.1rem 0.1rem 0.1rem 1rem;
|
|
217
217
|
margin-top: 0.3rem;
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
color: var(--lightGrey);
|
|
243
243
|
text-decoration: none;
|
|
244
244
|
cursor: pointer;
|
|
245
|
-
font-size:
|
|
245
|
+
font-size: 0.875rem;
|
|
246
246
|
font-weight: var(--font-semi-bold);
|
|
247
247
|
border-bottom: 1px solid var(--darkBlue);
|
|
248
248
|
|
|
@@ -274,6 +274,10 @@
|
|
|
274
274
|
justify-content: end;
|
|
275
275
|
margin-bottom: 1rem;
|
|
276
276
|
}
|
|
277
|
+
|
|
278
|
+
.extra-actions {
|
|
279
|
+
justify-content: end;
|
|
280
|
+
}
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
.forms-container {
|
|
@@ -283,7 +287,7 @@
|
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
.form-title {
|
|
286
|
-
font-size:
|
|
290
|
+
font-size: 1.375rem;
|
|
287
291
|
margin-bottom: 2%;
|
|
288
292
|
color: var(--darkGrey);
|
|
289
293
|
text-align: center;
|
|
@@ -329,7 +333,7 @@
|
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
& h2 {
|
|
332
|
-
font-size:
|
|
336
|
+
font-size: 1.375rem;
|
|
333
337
|
margin: 0 0 2rem;
|
|
334
338
|
color: var(--darkGrey);
|
|
335
339
|
text-align: center;
|
|
@@ -533,7 +537,7 @@
|
|
|
533
537
|
vertical-align: middle;
|
|
534
538
|
align-items: center;
|
|
535
539
|
margin: 0 1rem 1rem 0;
|
|
536
|
-
font-size:
|
|
540
|
+
font-size: 0.875rem;
|
|
537
541
|
color: var(--darkGrey);
|
|
538
542
|
|
|
539
543
|
&.filters-toggle {
|
|
@@ -650,7 +654,7 @@
|
|
|
650
654
|
|
|
651
655
|
.entity-view, .entity-edit {
|
|
652
656
|
& h2 {
|
|
653
|
-
font-size:
|
|
657
|
+
font-size: 1.375rem;
|
|
654
658
|
margin-bottom: 2rem;
|
|
655
659
|
color: var(--darkGrey);
|
|
656
660
|
text-align: center;
|
|
@@ -691,6 +695,10 @@
|
|
|
691
695
|
margin-left: 0.5rem;
|
|
692
696
|
margin-top: 0.5rem;
|
|
693
697
|
}
|
|
698
|
+
|
|
699
|
+
.remove-upload-btn {
|
|
700
|
+
cursor: pointer;
|
|
701
|
+
}
|
|
694
702
|
}
|
|
695
703
|
}
|
|
696
704
|
}
|
|
@@ -757,7 +765,7 @@
|
|
|
757
765
|
.extra-actions {
|
|
758
766
|
display: flex;
|
|
759
767
|
width: 100%;
|
|
760
|
-
justify-content:
|
|
768
|
+
justify-content: center;
|
|
761
769
|
margin: 1rem 0;
|
|
762
770
|
}
|
|
763
771
|
|
|
@@ -804,7 +812,7 @@
|
|
|
804
812
|
|
|
805
813
|
.dialog-content h5 {
|
|
806
814
|
margin: 0 0 1rem 0;
|
|
807
|
-
font-size:
|
|
815
|
+
font-size: 1.125rem;
|
|
808
816
|
color: var(--darkGrey);
|
|
809
817
|
}
|
|
810
818
|
|
|
@@ -242,6 +242,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
242
242
|
? 'Success!'
|
|
243
243
|
: 'There was an error: '+escapeHTML(errorMessages[result] || result);
|
|
244
244
|
deleteCookie('result');
|
|
245
|
+
queryParams.delete('result');
|
|
246
|
+
let newUrl = location.pathname + (queryParams.toString() ? '?' + queryParams.toString() : '');
|
|
247
|
+
window.history.replaceState({}, '', newUrl);
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
|
|
@@ -282,4 +285,35 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
282
285
|
});
|
|
283
286
|
}
|
|
284
287
|
|
|
288
|
+
// remove upload button functionality:
|
|
289
|
+
let removeUploadButtons = document.querySelectorAll('.remove-upload-btn');
|
|
290
|
+
if(removeUploadButtons){
|
|
291
|
+
for(let button of removeUploadButtons){
|
|
292
|
+
button.addEventListener('click', (event) => {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
let fieldName = button.getAttribute('data-field');
|
|
295
|
+
let fileInput = document.getElementById(fieldName);
|
|
296
|
+
let currentFileDisplay = document.querySelector('.upload-current-file[data-field="'+fieldName+'"]');
|
|
297
|
+
if(currentFileDisplay){
|
|
298
|
+
currentFileDisplay.style.display = 'none';
|
|
299
|
+
}
|
|
300
|
+
if(fileInput){
|
|
301
|
+
fileInput.value = '';
|
|
302
|
+
let form = fileInput.closest('form');
|
|
303
|
+
if(form){
|
|
304
|
+
let clearFieldName = 'clear_'+fieldName;
|
|
305
|
+
let existingClearInput = form.querySelector('input[name="'+clearFieldName+'"]');
|
|
306
|
+
if(!existingClearInput){
|
|
307
|
+
let clearInput = document.createElement('input');
|
|
308
|
+
clearInput.type = 'hidden';
|
|
309
|
+
clearInput.name = clearFieldName;
|
|
310
|
+
clearInput.value = '1';
|
|
311
|
+
form.appendChild(clearInput);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
285
319
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<form class="cache-clean-form" method="POST" action="{{&cacheCleanRoute}}">
|
|
2
2
|
<input type="hidden" name="routeId" value="{{routeId}}"/>
|
|
3
|
+
<input type="hidden" name="refererUrl" value="{{&refererUrl}}"/>
|
|
3
4
|
<button class="button button-warning cache-clean-btn" type="submit">{{&buttonText}}</button>
|
|
4
5
|
</form>
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
<button class="button button-primary button-submit" type="submit" form="edit-form" name="saveAction" value="saveAndGoBack">Save and go back</button>
|
|
7
7
|
<a class="button button-secondary button-back" href="{{&entityViewRoute}}">Cancel</a>
|
|
8
8
|
</div>
|
|
9
|
+
<div class="extra-actions">
|
|
10
|
+
{{&extraContent}}
|
|
11
|
+
</div>
|
|
9
12
|
<form name="edit-form" id="edit-form" action="{{&entitySaveRoute}}" method="post"{{&multipartFormData}}>
|
|
10
13
|
<input type="hidden" name="{{&idProperty}}" value="{{&idValue}}"/>
|
|
11
14
|
<div class="edit-field">
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<p>{{&fieldValue}}</p>
|
|
1
|
+
{{#fieldValue}}<p class="upload-current-file" data-field="{{&fieldName}}"><button type="button" class="remove-upload-btn" data-field="{{&fieldName}}" title="REMOVE">X</button> - {{&fieldValue}}</p>{{/fieldValue}}
|
|
2
2
|
<input type="file" name="{{&fieldName}}" id="{{&fieldName}}"{{&required}}{{&fieldDisabled}}{{&multiple}}/>
|
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
<a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
|
|
5
5
|
<a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
|
|
6
6
|
<a class="button button-secondary" href="{{&entityListRoute}}">Back</a>
|
|
7
|
-
<form class="form-delete" name="delete-form-top-{{&id}}"
|
|
7
|
+
<form class="form-delete" name="delete-form-top-{{&id}}" action="{{&entityDeleteRoute}}" method="post">
|
|
8
8
|
<span>
|
|
9
9
|
<input type="hidden" name="ids[]" value="{{&id}}"/>
|
|
10
10
|
<button class="button button-danger" type="submit">Delete</button>
|
|
11
11
|
</span>
|
|
12
12
|
</form>
|
|
13
13
|
</div>
|
|
14
|
+
<div class="extra-actions">
|
|
15
|
+
{{&extraContent}}
|
|
16
|
+
</div>
|
|
14
17
|
{{#fields}}
|
|
15
18
|
<div class="view-field">
|
|
16
19
|
<span class="field-name">{{&name}}</span>
|
|
@@ -22,7 +25,7 @@
|
|
|
22
25
|
<a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
|
|
23
26
|
<a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
|
|
24
27
|
<a class="button button-secondary" href="{{&entityListRoute}}">Back</a>
|
|
25
|
-
<form class="form-delete" name="delete-form-{{&id}}"
|
|
28
|
+
<form class="form-delete" name="delete-form-{{&id}}" action="{{&entityDeleteRoute}}" method="post">
|
|
26
29
|
<span>
|
|
27
30
|
<input type="hidden" name="ids[]" value="{{&id}}"/>
|
|
28
31
|
<button class="button button-danger" type="submit">Delete</button>
|
|
@@ -473,6 +473,8 @@ class RouterContents
|
|
|
473
473
|
if(!shouldProcess){
|
|
474
474
|
continue;
|
|
475
475
|
}
|
|
476
|
+
let clearFieldParam = sc.get(req.body, 'clear_'+i, false);
|
|
477
|
+
let isExplicitClear = '1' === clearFieldParam;
|
|
476
478
|
let propertyUpdateValue = sc.get(req.body, i, null);
|
|
477
479
|
if('null' === propertyUpdateValue){
|
|
478
480
|
propertyUpdateValue = null;
|
|
@@ -480,7 +482,12 @@ class RouterContents
|
|
|
480
482
|
let propertyType = sc.get(property, 'type', 'string');
|
|
481
483
|
if(property.isUpload){
|
|
482
484
|
propertyType = 'upload';
|
|
483
|
-
|
|
485
|
+
if(isExplicitClear){
|
|
486
|
+
propertyUpdateValue = null;
|
|
487
|
+
}
|
|
488
|
+
if(!isExplicitClear){
|
|
489
|
+
propertyUpdateValue = this.prepareUploadPatchData(req, i, propertyUpdateValue, property);
|
|
490
|
+
}
|
|
484
491
|
}
|
|
485
492
|
if('boolean' === propertyType){
|
|
486
493
|
propertyUpdateValue = '1' === propertyUpdateValue || 'on' === propertyUpdateValue;
|
|
@@ -518,7 +525,7 @@ class RouterContents
|
|
|
518
525
|
continue;
|
|
519
526
|
}
|
|
520
527
|
}
|
|
521
|
-
if(!property.isUpload || (property.isUpload && !isNull)){
|
|
528
|
+
if(!property.isUpload || (property.isUpload && (!isNull || isExplicitClear))){
|
|
522
529
|
entityDataPatch[i] = propertyUpdateValue;
|
|
523
530
|
}
|
|
524
531
|
}
|
|
@@ -546,13 +553,13 @@ class RouterContents
|
|
|
546
553
|
if(resourceProperty.isArray){
|
|
547
554
|
fieldValue = fieldValue.split(resourceProperty.isArray).map((value) => {
|
|
548
555
|
let target = resourceProperty.isUpload ? ' target="_blank"' : '';
|
|
549
|
-
let fieldValuePart = resourceProperty.isUpload && resourceProperty.bucketPath
|
|
556
|
+
let fieldValuePart = resourceProperty.isUpload && resourceProperty.bucketPath && value
|
|
550
557
|
? resourceProperty.bucketPath+value
|
|
551
558
|
: value;
|
|
552
559
|
return {fieldValuePart, fieldOriginalValuePart: value, target};
|
|
553
560
|
});
|
|
554
561
|
}
|
|
555
|
-
if(!resourceProperty.isArray && resourceProperty.isUpload){
|
|
562
|
+
if(!resourceProperty.isArray && resourceProperty.isUpload && fieldValue){
|
|
556
563
|
fieldValue = resourceProperty.bucketPath+fieldValue;
|
|
557
564
|
}
|
|
558
565
|
}
|
|
@@ -87,7 +87,8 @@ class AddCacheButtonSubscriber
|
|
|
87
87
|
|
|
88
88
|
async generateCacheCleanButton(event)
|
|
89
89
|
{
|
|
90
|
-
|
|
90
|
+
let entityId = event.driverResource.id();
|
|
91
|
+
if('routes' !== entityId && 'cms_pages' !== entityId){
|
|
91
92
|
return false;
|
|
92
93
|
}
|
|
93
94
|
if(!event.loadedEntity){
|
|
@@ -98,6 +99,13 @@ class AddCacheButtonSubscriber
|
|
|
98
99
|
Logger.error('Missing loaded entity ID on AddCacheButtonSubscriber.');
|
|
99
100
|
return false;
|
|
100
101
|
}
|
|
102
|
+
let routeId = event.loadedEntity.id;
|
|
103
|
+
if('cms_pages' === entityId){
|
|
104
|
+
if(!event.loadedEntity.route_id){
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
routeId = event.loadedEntity.route_id;
|
|
108
|
+
}
|
|
101
109
|
if(!this.cacheCleanButton){
|
|
102
110
|
Logger.error('Cache clean button template content not found');
|
|
103
111
|
return '';
|
|
@@ -106,11 +114,13 @@ class AddCacheButtonSubscriber
|
|
|
106
114
|
Logger.error('Render callback not available for cache button');
|
|
107
115
|
return '';
|
|
108
116
|
}
|
|
117
|
+
let refererUrl = sc.get(event.req, 'originalUrl', '');
|
|
109
118
|
return await this.renderCallback(
|
|
110
119
|
this.cacheCleanButton,
|
|
111
120
|
{
|
|
112
121
|
cacheCleanRoute: this.cacheCleanRoute,
|
|
113
|
-
routeId:
|
|
122
|
+
routeId: routeId,
|
|
123
|
+
refererUrl: refererUrl,
|
|
114
124
|
buttonText: sc.get(this.translations.labels, 'cleanCache', 'cleanCache')
|
|
115
125
|
}
|
|
116
126
|
);
|
|
@@ -135,7 +145,11 @@ class AddCacheButtonSubscriber
|
|
|
135
145
|
buttonText: sc.get(this.translations.labels, 'clearAllCache', 'clearAllCache'),
|
|
136
146
|
clearAllCacheRoute: this.clearAllCacheRoute,
|
|
137
147
|
confirmTitle: sc.get(this.translations.labels, 'confirmClearCache', 'confirmClearCache'),
|
|
138
|
-
confirmMessage: sc.get(
|
|
148
|
+
confirmMessage: sc.get(
|
|
149
|
+
this.translations.messages,
|
|
150
|
+
'confirmClearCacheMessage',
|
|
151
|
+
'confirmClearCacheMessage'
|
|
152
|
+
),
|
|
139
153
|
warningText: sc.get(this.translations.labels, 'warning', 'warning'),
|
|
140
154
|
warningMessage: sc.get(this.translations.messages, 'clearCacheWarning', 'clearCacheWarning'),
|
|
141
155
|
cancelText: sc.get(this.translations.labels, 'cancel', 'cancel'),
|
|
@@ -15,6 +15,21 @@ class CacheManager
|
|
|
15
15
|
this.projectRoot = sc.get(props, 'projectRoot', './');
|
|
16
16
|
this.cacheBasePath = FileHandler.joinPaths(this.projectRoot, '.reldens_cms_cache');
|
|
17
17
|
this.enabled = sc.get(props, 'enabled', true);
|
|
18
|
+
this.domainMapping = sc.get(props, 'domainMapping', {});
|
|
19
|
+
this.reverseDomainMapping = this.buildReverseDomainMapping();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
buildReverseDomainMapping()
|
|
23
|
+
{
|
|
24
|
+
let reverse = {};
|
|
25
|
+
for(let domain in this.domainMapping){
|
|
26
|
+
let canonical = this.domainMapping[domain];
|
|
27
|
+
if(!reverse[canonical]){
|
|
28
|
+
reverse[canonical] = [];
|
|
29
|
+
}
|
|
30
|
+
reverse[canonical].push(domain);
|
|
31
|
+
}
|
|
32
|
+
return reverse;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
generateCacheKey(domain, path)
|
|
@@ -93,22 +108,22 @@ class CacheManager
|
|
|
93
108
|
|
|
94
109
|
async delete(domain, path)
|
|
95
110
|
{
|
|
96
|
-
let cacheByDomains = this.
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
let cacheByDomains = this.resolveCacheDomains(domain);
|
|
112
|
+
if(0 === cacheByDomains.length){
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
for(let domainInfo of cacheByDomains){
|
|
116
|
+
let actualDomain = domainInfo.domain;
|
|
117
|
+
let allCacheFiles = this.findAllCacheFilesForPath(actualDomain, path);
|
|
99
118
|
if(0 === allCacheFiles.length){
|
|
100
|
-
let singleCacheInfo = this.generateCacheKey(
|
|
119
|
+
let singleCacheInfo = this.generateCacheKey(actualDomain, path);
|
|
101
120
|
if(!FileHandler.exists(singleCacheInfo.fullPath)){
|
|
102
|
-
Logger.debug('No cache files found for: '+path);
|
|
121
|
+
//Logger.debug('No cache files found for: '+path+' in domain: '+actualDomain);
|
|
103
122
|
continue;
|
|
104
123
|
}
|
|
105
124
|
allCacheFiles = [singleCacheInfo.fullPath];
|
|
106
125
|
}
|
|
107
126
|
for(let cacheFilePath of allCacheFiles){
|
|
108
|
-
if(!FileHandler.exists(cacheFilePath)){
|
|
109
|
-
Logger.debug('File does not exist: '+cacheFilePath);
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
127
|
if(!FileHandler.remove(cacheFilePath)){
|
|
113
128
|
Logger.error('Failed to delete cache file: '+cacheFilePath);
|
|
114
129
|
return false;
|
|
@@ -119,19 +134,22 @@ class CacheManager
|
|
|
119
134
|
return true;
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
resolveCacheDomains(domain)
|
|
123
138
|
{
|
|
124
139
|
let cacheByDomains = [];
|
|
125
|
-
if(domain
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
if(!domain || 'default' === domain){
|
|
141
|
+
if(!FileHandler.exists(this.cacheBasePath)){
|
|
142
|
+
return cacheByDomains;
|
|
143
|
+
}
|
|
144
|
+
let cachedDomainFolders = FileHandler.readFolder(this.cacheBasePath);
|
|
145
|
+
for(let cachedDomainFolder of cachedDomainFolders) {
|
|
146
|
+
cacheByDomains.push({domain: cachedDomainFolder});
|
|
147
|
+
}
|
|
130
148
|
return cacheByDomains;
|
|
131
149
|
}
|
|
132
|
-
let
|
|
133
|
-
for(let
|
|
134
|
-
cacheByDomains.push({domain:
|
|
150
|
+
let domainsToDelete = this.reverseDomainMapping[domain] || [domain];
|
|
151
|
+
for(let mappedDomain of domainsToDelete){
|
|
152
|
+
cacheByDomains.push({domain: mappedDomain});
|
|
135
153
|
}
|
|
136
154
|
return cacheByDomains;
|
|
137
155
|
}
|
|
@@ -77,7 +77,10 @@ class CacheRoutesHandler
|
|
|
77
77
|
if(!cleanResult){
|
|
78
78
|
return res.json({error: 'Failed to clean cache'});
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
let defaultRedirect = this.rootPath+'/routes/view?id='+routeId;
|
|
81
|
+
let refererUrl = sc.get(req.body, 'refererUrl', defaultRedirect);
|
|
82
|
+
let redirectUrl = refererUrl+(refererUrl.includes('?') ? '&' : '?')+'result=success';
|
|
83
|
+
return res.redirect(redirectUrl);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
async processClearAllCache(req, res)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - EntitiesConfigProcessor
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class EntitiesConfigProcessor
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} baseEntitiesConfig
|
|
14
|
+
* @param {Object} overrides
|
|
15
|
+
* @param {Object} props
|
|
16
|
+
* @returns {Object}
|
|
17
|
+
*/
|
|
18
|
+
static applyOverrides(baseEntitiesConfig, overrides, props = {})
|
|
19
|
+
{
|
|
20
|
+
if(!overrides || 'object' !== typeof overrides || 0 === Object.keys(overrides).length){
|
|
21
|
+
return baseEntitiesConfig;
|
|
22
|
+
}
|
|
23
|
+
let mergedConfig = sc.deepMergeProperties({}, baseEntitiesConfig);
|
|
24
|
+
for(let entityKey of Object.keys(overrides)){
|
|
25
|
+
let override = overrides[entityKey];
|
|
26
|
+
if(!override){
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let baseConfig = mergedConfig[entityKey] || {};
|
|
30
|
+
mergedConfig[entityKey] = this.applyEntityOverride(entityKey, baseConfig, override, props);
|
|
31
|
+
}
|
|
32
|
+
return mergedConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} entityKey
|
|
37
|
+
* @param {Object} baseConfig
|
|
38
|
+
* @param {*} override
|
|
39
|
+
* @param {Object} props
|
|
40
|
+
* @returns {Object}
|
|
41
|
+
*/
|
|
42
|
+
static applyEntityOverride(entityKey, baseConfig, override, props)
|
|
43
|
+
{
|
|
44
|
+
if('function' === typeof override && sc.hasOwn(override, 'propertiesConfig')){
|
|
45
|
+
return override.propertiesConfig(baseConfig, props);
|
|
46
|
+
}
|
|
47
|
+
if(sc.isFunction(override)){
|
|
48
|
+
return override(baseConfig, props);
|
|
49
|
+
}
|
|
50
|
+
if('object' === typeof override){
|
|
51
|
+
return sc.deepMergeProperties({}, baseConfig, override);
|
|
52
|
+
}
|
|
53
|
+
Logger.warning('Invalid override type for entity: '+entityKey);
|
|
54
|
+
return baseConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports.EntitiesConfigProcessor = EntitiesConfigProcessor;
|
package/lib/manager.js
CHANGED
|
@@ -13,6 +13,7 @@ const { AllowedExtensions } = require('./allowed-extensions');
|
|
|
13
13
|
const { TemplatesToPathMapper } = require('./templates-to-path-mapper');
|
|
14
14
|
const { AdminEntitiesGenerator } = require('./admin-entities-generator');
|
|
15
15
|
const { LoadedEntitiesProcessor } = require('./loaded-entities-processor');
|
|
16
|
+
const { EntitiesConfigProcessor } = require('./entities-config-processor');
|
|
16
17
|
const { AdminManager } = require('./admin-manager');
|
|
17
18
|
const { CmsPagesRouteManager } = require('./cms-pages-route-manager');
|
|
18
19
|
const { Installer } = require('./installer');
|
|
@@ -41,6 +42,7 @@ class Manager
|
|
|
41
42
|
this.rawRegisteredEntities = sc.get(props, 'rawRegisteredEntities', {});
|
|
42
43
|
this.entitiesTranslations = sc.get(props, 'entitiesTranslations', {});
|
|
43
44
|
this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
|
|
45
|
+
this.entitiesConfigOverride = sc.get(props, 'entitiesConfigOverride', {});
|
|
44
46
|
this.processedEntities = sc.get(props, 'processedEntities', {});
|
|
45
47
|
this.entityAccess = sc.get(props, 'entityAccess', {});
|
|
46
48
|
this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
|
|
@@ -92,7 +94,11 @@ class Manager
|
|
|
92
94
|
this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
|
|
93
95
|
this.appServerFactory = new AppServerFactory();
|
|
94
96
|
this.adminEntitiesGenerator = new AdminEntitiesGenerator();
|
|
95
|
-
this.cacheManager = new CacheManager({
|
|
97
|
+
this.cacheManager = new CacheManager({
|
|
98
|
+
projectRoot: this.projectRoot,
|
|
99
|
+
enabled: this.cache,
|
|
100
|
+
domainMapping: this.domainMapping
|
|
101
|
+
});
|
|
96
102
|
this.templateReloader = new TemplateReloader({
|
|
97
103
|
reloadTime: this.reloadTime,
|
|
98
104
|
events: this.events,
|
|
@@ -439,10 +445,15 @@ class Manager
|
|
|
439
445
|
loadProcessedEntities()
|
|
440
446
|
{
|
|
441
447
|
if(0 === Object.keys(this.processedEntities).length){
|
|
448
|
+
let mergedConfig = EntitiesConfigProcessor.applyOverrides(
|
|
449
|
+
this.entitiesConfig,
|
|
450
|
+
this.entitiesConfigOverride,
|
|
451
|
+
{projectRoot: this.projectRoot}
|
|
452
|
+
);
|
|
442
453
|
this.processedEntities = LoadedEntitiesProcessor.process(
|
|
443
454
|
this.rawRegisteredEntities,
|
|
444
455
|
this.entitiesTranslations,
|
|
445
|
-
|
|
456
|
+
mergedConfig
|
|
446
457
|
);
|
|
447
458
|
}
|
|
448
459
|
if(!this.processedEntities?.entities){
|
package/lib/template-reloader.js
CHANGED
|
@@ -247,12 +247,12 @@ class TemplateReloader
|
|
|
247
247
|
if(!this.shouldReloadAdminTemplates(this.mappedAdminTemplates)){
|
|
248
248
|
return false;
|
|
249
249
|
}
|
|
250
|
-
let
|
|
251
|
-
if(!
|
|
250
|
+
let adminFilesContents = await this.adminTemplatesLoader.fetchAdminFilesContents(this.mappedAdminTemplates);
|
|
251
|
+
if(!adminFilesContents){
|
|
252
252
|
return false;
|
|
253
253
|
}
|
|
254
254
|
this.markTemplatesAsReloaded(this.mappedAdminTemplates);
|
|
255
|
-
return
|
|
255
|
+
return adminFilesContents;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
async checkAndReloadFrontendTemplates()
|
|
@@ -265,16 +265,16 @@ class TemplateReloader
|
|
|
265
265
|
return true;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
async updateAdminContentsAfterReload(
|
|
268
|
+
async updateAdminContentsAfterReload(adminFilesContents, adminManager)
|
|
269
269
|
{
|
|
270
|
-
if(!
|
|
270
|
+
if(!adminFilesContents || !adminManager){
|
|
271
271
|
return false;
|
|
272
272
|
}
|
|
273
|
-
adminManager.adminFilesContents =
|
|
274
|
-
adminManager.contentsBuilder.adminFilesContents =
|
|
275
|
-
adminManager.routerContents.adminFilesContents =
|
|
276
|
-
adminManager.addCacheButtonSubscriber.cacheCleanButton =
|
|
277
|
-
adminManager.addCacheButtonSubscriber.clearAllCacheButton =
|
|
273
|
+
adminManager.adminFilesContents = adminFilesContents;
|
|
274
|
+
adminManager.contentsBuilder.adminFilesContents = adminFilesContents;
|
|
275
|
+
adminManager.routerContents.adminFilesContents = adminFilesContents;
|
|
276
|
+
adminManager.addCacheButtonSubscriber.cacheCleanButton = adminFilesContents.cacheCleanButton;
|
|
277
|
+
adminManager.addCacheButtonSubscriber.clearAllCacheButton = adminFilesContents.clearAllCacheButton;
|
|
278
278
|
await adminManager.contentsBuilder.buildAdminContents();
|
|
279
279
|
return true;
|
|
280
280
|
}
|