@reldens/cms 0.20.0 → 0.23.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.
Files changed (67) hide show
  1. package/README.md +648 -12
  2. package/admin/reldens-admin-client.css +77 -141
  3. package/admin/reldens-admin-client.js +108 -133
  4. package/admin/templates/clear-all-cache-button.html +7 -7
  5. package/admin/templates/edit.html +7 -0
  6. package/admin/templates/fields/view/audio.html +7 -0
  7. package/admin/templates/fields/view/audios.html +8 -0
  8. package/admin/templates/layout.html +15 -9
  9. package/admin/templates/list-content.html +4 -2
  10. package/admin/templates/list.html +24 -8
  11. package/admin/templates/view.html +21 -0
  12. package/install/index.html +4 -0
  13. package/lib/admin-manager/admin-filters-manager.js +177 -0
  14. package/lib/admin-manager/contents-builder.js +1 -0
  15. package/lib/admin-manager/default-translations.js +38 -0
  16. package/lib/admin-manager/router-contents.js +64 -52
  17. package/lib/admin-manager/router.js +19 -0
  18. package/lib/dynamic-form-renderer.js +228 -0
  19. package/lib/dynamic-form-request-handler.js +135 -0
  20. package/lib/dynamic-form.js +310 -0
  21. package/lib/frontend/content-renderer.js +178 -0
  22. package/lib/frontend/entity-access-manager.js +63 -0
  23. package/lib/frontend/request-processor.js +128 -0
  24. package/lib/frontend/response-manager.js +54 -0
  25. package/lib/frontend/template-cache.js +102 -0
  26. package/lib/frontend/template-resolver.js +111 -0
  27. package/lib/frontend.js +122 -629
  28. package/lib/installer.js +2 -1
  29. package/lib/manager.js +25 -12
  30. package/lib/search-renderer.js +15 -7
  31. package/lib/search-request-handler.js +67 -0
  32. package/lib/search.js +13 -1
  33. package/lib/template-engine/collections-single-transformer.js +11 -5
  34. package/lib/template-engine/collections-transformer.js +47 -34
  35. package/lib/template-engine/entities-transformer.js +3 -2
  36. package/lib/template-engine/forms-transformer.js +187 -0
  37. package/lib/template-engine/partials-transformer.js +5 -6
  38. package/lib/template-engine/system-variables-provider.js +4 -1
  39. package/lib/template-engine.js +28 -5
  40. package/lib/template-reloader.js +307 -0
  41. package/lib/templates-list.js +2 -0
  42. package/migrations/default-forms.sql +22 -0
  43. package/package.json +5 -5
  44. package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
  45. package/templates/assets/favicons/default/favicon.ico +0 -0
  46. package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
  47. package/templates/cms_forms/field_email.html +14 -0
  48. package/templates/cms_forms/field_number.html +17 -0
  49. package/templates/cms_forms/field_select.html +15 -0
  50. package/templates/cms_forms/field_text.html +16 -0
  51. package/templates/cms_forms/field_textarea.html +13 -0
  52. package/templates/cms_forms/form.html +22 -0
  53. package/templates/css/styles.css +4 -0
  54. package/templates/js/functions.js +144 -0
  55. package/templates/js/scripts.js +5 -0
  56. package/templates/page.html +11 -5
  57. package/templates/partials/pagedCollection.html +1 -1
  58. package/lib/admin-translations.js +0 -56
  59. package/templates/favicon.ico +0 -0
  60. /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
  61. /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
  62. /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
  63. /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
  64. /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
  65. /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
  66. /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
  67. /package/templates/assets/favicons/{safari-pinned-tab.svg → default/safari-pinned-tab.svg} +0 -0
@@ -1,15 +1,21 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
4
  <title>{{&brandingCompanyName}}</title>
5
- <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Play:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet"/>
6
- <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.6.0/firebase-ui-auth.css"/>
7
- <link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon.png"/>
8
- <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicons/favicon-32x32.png"/>
9
- <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicons/favicon-16x16.png"/>
10
- <link rel="manifest" href="/site.webmanifest"/>
11
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
12
- <link rel="stylesheet" type="text/css" href="{{stylesFilePath}}"/>
5
+ <meta name="language" content="en"/>
6
+ <meta charset="utf-8"/>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover"/>
8
+ <!-- favicons -->
9
+ <meta name="msapplication-config" content="/assets/favicons/default/browserconfig.xml"/>
10
+ <link rel="icon" href="/assets/favicons/default/favicon.ico" type="image/x-icon"/>
11
+ <link rel="shortcut icon" href="/assets/favicons/default/favicon.ico" type="image/x-icon">
12
+ <link rel="manifest" href="/assets/favicons/default/site.webmanifest"/>
13
+ <!-- CSS and JS -->
14
+ <link crossorigin rel="preconnect" href="https://fonts.googleapis.com"/>
15
+ <link crossorigin rel="preconnect" href="https://fonts.gstatic.com"/>
16
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Play:300,300i,400,400i,500,500i,600,600i,700,700i"/>
17
+ <link rel="stylesheet" href="{{stylesFilePath}}"/>
18
+ <script type="text/javascript" src="/js/functions.js"></script>
13
19
  <script type="module" src="{{scriptsFilePath}}"></script>
14
20
  </head>
15
21
  <body class="reldens-admin-panel">
@@ -1,11 +1,13 @@
1
1
  <tr class="row row-header">
2
2
  <th class="field field-actions">
3
3
  <div class="field-actions-container">
4
- <button class="button button-primary list-select" data-checked="1">Select All/None</button>
5
4
  <form name="delete-selection-form" id="delete-selection-form" action="{{&deletePath}}" method="post">
6
5
  <input type="hidden" name="ids" class="hidden-ids-input"/>
7
- <button class="button button-danger list-delete-selection">Delete Selection</button>
6
+ <button class="button button-danger list-delete-selection">Bulk Delete</button>
8
7
  </form>
8
+ <button class="button button-primary list-select" data-checked="1">
9
+ <input type="checkbox" id="input-check-select-all"/>
10
+ </button>
9
11
  </div>
10
12
  </th>
11
13
  {{#fieldsHeaders}}
@@ -8,10 +8,20 @@
8
8
  </div>
9
9
  <div class="sub-content filters">
10
10
  <form class="sub-content-form filter-form" name="filter-form" id="filter-form" action="{{&entityListRoute}}" method="post">
11
- <h4 class="filters-toggle">
12
- <img src="/assets/admin/filters.png" alt="Filters"/>
13
- <span>Filters</span>
14
- </h4>
11
+ <div class="filters-header">
12
+ <div class="search-controls">
13
+ <div class="filters-toggle">
14
+ <img src="/assets/admin/filters.png" alt="Filters"/>
15
+ </div>
16
+ <div class="search-input-group">
17
+ <input type="text" id="entityFilterTerm" name="entityFilterTerm" placeholder="Search..." value="{{&entityFilterTermValue}}"/>
18
+ </div>
19
+ <div class="filter-actions">
20
+ <input class="button button-primary button-filter" type="submit" value="Filter"/>
21
+ <a class="button button-secondary button-clear-filter" href="{{&entityListRoute}}?clearFilters=true">Clear Filters</a>
22
+ </div>
23
+ </div>
24
+ </div>
15
25
  <div class="filters-toggle-content hidden">
16
26
  {{#filters}}
17
27
  <div class="sub-content-box filter">
@@ -19,10 +29,6 @@
19
29
  <input type="text" id="filter-{{&propertyKey}}" name="filters[{{&propertyKey}}]"{{&value}}/>
20
30
  </div>
21
31
  {{/filters}}
22
- <div class="actions">
23
- <input class="button button-primary button-filter" type="submit" value="Filter"/>
24
- <a class="button button-secondary button-clear-filter" href="{{&entityListRoute}}">Clear Filters</a>
25
- </div>
26
32
  </div>
27
33
  </form>
28
34
  </div>
@@ -33,3 +39,13 @@
33
39
  {{&pagination}}
34
40
  </div>
35
41
  </div>
42
+ <dialog class="confirm-dialog">
43
+ <div class="dialog-content">
44
+ <h5 class="dialog-title">Confirm Delete</h5>
45
+ <p class="dialog-message">Are you sure you want to delete the selected items? This action cannot be undone.</p>
46
+ <div class="dialog-actions">
47
+ <button type="button" class="button button-secondary dialog-cancel">Cancel</button>
48
+ <button type="button" class="button button-danger dialog-confirm">Delete</button>
49
+ </div>
50
+ </div>
51
+ </dialog>
@@ -1,5 +1,16 @@
1
1
  <div class="entity-view {{&entityName}}-view">
2
2
  <h2>{{&templateTitle}}</h2>
3
+ <div class="actions">
4
+ <a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
5
+ <a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
6
+ <a class="button button-secondary" href="{{&entityListRoute}}">Back</a>
7
+ <form class="form-delete" name="delete-form-top-{{&id}}" id="delete-form-top-{{&id}}" action="{{&entityDeleteRoute}}" method="post">
8
+ <span>
9
+ <input type="hidden" name="ids[]" value="{{&id}}"/>
10
+ <button class="button button-danger" type="submit">Delete</button>
11
+ </span>
12
+ </form>
13
+ </div>
3
14
  {{#fields}}
4
15
  <div class="view-field">
5
16
  <span class="field-name">{{&name}}</span>
@@ -22,3 +33,13 @@
22
33
  </div>
23
34
  </div>
24
35
  </div>
36
+ <dialog class="confirm-dialog">
37
+ <div class="dialog-content">
38
+ <h5 class="dialog-title">Confirm Delete</h5>
39
+ <p class="dialog-message">Are you sure you want to delete this item? This action cannot be undone.</p>
40
+ <div class="dialog-actions">
41
+ <button type="button" class="button button-secondary dialog-cancel">Cancel</button>
42
+ <button type="button" class="button button-danger dialog-confirm">Delete</button>
43
+ </div>
44
+ </div>
45
+ </dialog>
@@ -101,6 +101,10 @@
101
101
  <input type="checkbox" name="install-entity-access" id="install-entity-access" checked/>
102
102
  <label for="install-entity-access">Install entity access control rules</label>
103
103
  </div>
104
+ <div class="input-box input-checkbox install-dynamic-forms">
105
+ <input type="checkbox" name="install-dynamic-forms" id="install-dynamic-forms" checked/>
106
+ <label for="install-dynamic-forms">Install dynamic forms system</label>
107
+ </div>
104
108
  <div class="input-box submit-container">
105
109
  <input id="install-submit-button" type="submit" value="Install"/>
106
110
  <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
@@ -0,0 +1,177 @@
1
+ /**
2
+ *
3
+ * Reldens - AdminFiltersManager
4
+ *
5
+ */
6
+
7
+ const { Logger } = require('@reldens/utils');
8
+
9
+ class AdminFiltersManager
10
+ {
11
+
12
+ getFiltersFromSession(req, entityPath)
13
+ {
14
+ if(!req?.session){
15
+ return {regular: {}, entityFilterTerm: ''};
16
+ }
17
+ let sessionKey = 'adminFilters_'+entityPath;
18
+ let sessionData = req.session[sessionKey];
19
+ if(!sessionData || 'object' !== typeof sessionData){
20
+ return {regular: {}, entityFilterTerm: ''};
21
+ }
22
+ return {
23
+ regular: sessionData.regular || {},
24
+ entityFilterTerm: sessionData.entityFilterTerm || ''
25
+ };
26
+ }
27
+
28
+ saveFiltersToSession(req, entityPath, filters)
29
+ {
30
+ if(!req?.session){
31
+ return false;
32
+ }
33
+ let sessionKey = 'adminFilters_'+entityPath;
34
+ req.session[sessionKey] = filters;
35
+ return true;
36
+ }
37
+
38
+ clearFiltersFromSession(req, entityPath)
39
+ {
40
+ if(!req?.session){
41
+ return false;
42
+ }
43
+ let sessionKey = 'adminFilters_'+entityPath;
44
+ delete req.session[sessionKey];
45
+ return true;
46
+ }
47
+
48
+ prepareTextFilters(entityFilterTerm, driverResource)
49
+ {
50
+ if(!entityFilterTerm || '' === entityFilterTerm.trim()){
51
+ return {};
52
+ }
53
+ let textFields = [];
54
+ for(let propertyKey of Object.keys(driverResource.options.properties)){
55
+ let property = driverResource.options.properties[propertyKey];
56
+ if(property.isId){
57
+ continue;
58
+ }
59
+ if(property.isUpload){
60
+ continue;
61
+ }
62
+ if('reference' === property.type){
63
+ continue;
64
+ }
65
+ if('boolean' === property.type){
66
+ continue;
67
+ }
68
+ if('number' === property.type){
69
+ continue;
70
+ }
71
+ if('datetime' === property.type){
72
+ continue;
73
+ }
74
+ if('int' === property.dbType){
75
+ continue;
76
+ }
77
+ if('bigint' === property.dbType){
78
+ continue;
79
+ }
80
+ if('decimal' === property.dbType){
81
+ continue;
82
+ }
83
+ if('float' === property.dbType){
84
+ continue;
85
+ }
86
+ if('double' === property.dbType){
87
+ continue;
88
+ }
89
+ if('datetime' === property.dbType){
90
+ continue;
91
+ }
92
+ if('timestamp' === property.dbType){
93
+ continue;
94
+ }
95
+ if('date' === property.dbType){
96
+ continue;
97
+ }
98
+ if('time' === property.dbType){
99
+ continue;
100
+ }
101
+ if('boolean' === property.dbType){
102
+ continue;
103
+ }
104
+ textFields.push(propertyKey);
105
+ }
106
+ if(0 === textFields.length){
107
+ return {};
108
+ }
109
+ let orConditions = [];
110
+ for(let field of textFields){
111
+ orConditions.push({
112
+ [field]: {operator: 'LIKE', value: '%'+entityFilterTerm+'%'}
113
+ });
114
+ }
115
+ return {OR: orConditions};
116
+ }
117
+
118
+ combineFilters(regularFilters, textFilters)
119
+ {
120
+ let hasRegularFilters = regularFilters && 'object' === typeof regularFilters && 0 < Object.keys(regularFilters).length;
121
+ let hasTextFilters = textFilters && 'object' === typeof textFilters && 0 < Object.keys(textFilters).length;
122
+ if(!hasRegularFilters && !hasTextFilters){
123
+ return {};
124
+ }
125
+ if(!hasRegularFilters){
126
+ return textFilters;
127
+ }
128
+ if(!hasTextFilters){
129
+ return regularFilters;
130
+ }
131
+ return {
132
+ AND: [
133
+ regularFilters,
134
+ textFilters
135
+ ]
136
+ };
137
+ }
138
+
139
+ prepareFilters(filtersList, driverResource)
140
+ {
141
+ if(!filtersList || 'object' !== typeof filtersList || 0 === Object.keys(filtersList).length){
142
+ return {};
143
+ }
144
+ let filters = {};
145
+ for(let i of Object.keys(filtersList)){
146
+ let filter = filtersList[i];
147
+ if('' === filter || null === filter || undefined === filter){
148
+ continue;
149
+ }
150
+ let rawConfigFilterProperties = driverResource.options.properties[i];
151
+ if(!rawConfigFilterProperties){
152
+ Logger.critical('Could not found property by key.', i);
153
+ continue;
154
+ }
155
+ if(rawConfigFilterProperties.isUpload){
156
+ continue;
157
+ }
158
+ if('reference' === rawConfigFilterProperties.type){
159
+ filters[i] = Number(filter);
160
+ continue;
161
+ }
162
+ if('boolean' === rawConfigFilterProperties.type){
163
+ filters[i] = ('true' === filter || '1' === filter || 1 === filter);
164
+ continue;
165
+ }
166
+ if('number' === rawConfigFilterProperties.type || rawConfigFilterProperties.isId){
167
+ filters[i] = Number(filter);
168
+ continue;
169
+ }
170
+ filters[i] = {operator: 'LIKE', value: '%'+filter+'%'};
171
+ }
172
+ return filters;
173
+ }
174
+
175
+ }
176
+
177
+ module.exports.AdminFiltersManager = AdminFiltersManager;
@@ -167,6 +167,7 @@ class ContentsBuilder
167
167
  templateTitle,
168
168
  entityListRoute,
169
169
  entityEditRoute,
170
+ entityFilterTermValue: '{{&entityFilterTermValue}}',
170
171
  filters,
171
172
  list: '{{&list}}',
172
173
  pagination: '{{&pagination}}',
@@ -0,0 +1,38 @@
1
+ /**
2
+ *
3
+ * Reldens - DefaultTranslations
4
+ *
5
+ */
6
+
7
+ module.exports.DefaultTranslations = {
8
+ messages: {
9
+ loginWelcome: 'CMS - Login',
10
+ reldensTitle: 'Reldens - CMS',
11
+ reldensSlogan: 'You can do it',
12
+ reldensDiscordTitle: 'Join our Discord server!',
13
+ reldensDiscordText: 'Talk with the creators and other Reldens users',
14
+ reldensGithubTitle: 'Find us on GitHub!',
15
+ reldensGithubText: 'Need a new feature?'
16
+ +' Would you like to contribute with code?'
17
+ +' Find the source code or create an issue in GitHub',
18
+ reldensLoading: 'Loading...',
19
+ confirmClearCacheMessage: 'Are you sure you want to clear all cache? This action cannot be undone.',
20
+ clearCacheWarning: 'This will clear all cached routes and may impact site performance temporarily.'
21
+ },
22
+ labels: {
23
+ navigation: 'Reldens - CMS',
24
+ adminVersion: 'Admin: {{version}}',
25
+ loginWelcome: 'Reldens',
26
+ pages: 'Server Management',
27
+ management: 'Management',
28
+ shuttingDown: 'Server is shutting down in:',
29
+ submitShutdownLabel: 'Shutdown Server',
30
+ submitCancelLabel: 'Cancel Server Shutdown',
31
+ cleanCache: 'Clean Cache',
32
+ clearAllCache: 'Clear All Cache',
33
+ confirmClearCache: 'Confirm Clear Cache',
34
+ warning: 'Warning:',
35
+ cancel: 'Cancel',
36
+ confirm: 'Continue',
37
+ }
38
+ };
@@ -6,6 +6,7 @@
6
6
 
7
7
  const { PageRangeProvider, Logger, sc } = require('@reldens/utils');
8
8
  const { FileHandler } = require('@reldens/server-utils');
9
+ const { AdminFiltersManager } = require('./admin-filters-manager');
9
10
 
10
11
  class RouterContents
11
12
  {
@@ -30,29 +31,64 @@ class RouterContents
30
31
  this.fetchTranslation = props.fetchTranslation;
31
32
  this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
32
33
  this.fetchUploadProperties = props.fetchUploadProperties;
34
+ this.filtersManager = new AdminFiltersManager();
33
35
  }
34
36
 
35
37
  async generateListRouteContent(req, driverResource, entityPath)
36
38
  {
37
39
  let currentPage = Number(req?.query?.page || 1);
38
40
  let pageSize = Number(req?.query?.pageSize || 25);
39
- let filtersFromParams = req?.body?.filters || {};
40
- let filters = this.prepareFilters(filtersFromParams, driverResource);
41
- let totalEntities = await this.countTotalEntities(driverResource, filters);
41
+ let shouldClearFilters = 'true' === req?.query?.clearFilters;
42
+ let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
43
+ let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
44
+ let entityFilterTerm = shouldClearFilters ? '' : req?.body?.entityFilterTerm || req?.query?.entityFilterTerm || '';
45
+ if(shouldClearFilters){
46
+ this.filtersManager.clearFiltersFromSession(req, entityPath);
47
+ }
48
+ let hasNewEntityFilterTerm = entityFilterTerm && '' !== entityFilterTerm;
49
+ let hasNewToggleFilters = Object.keys(filtersFromParams).length > 0;
50
+ if(hasNewEntityFilterTerm && hasNewToggleFilters){
51
+ filtersFromParams = {};
52
+ hasNewToggleFilters = false;
53
+ }
54
+ if(hasNewEntityFilterTerm || hasNewToggleFilters){
55
+ let filtersToSave = {
56
+ regular: hasNewToggleFilters ? filtersFromParams : {},
57
+ entityFilterTerm: hasNewEntityFilterTerm ? entityFilterTerm : ''
58
+ };
59
+ this.filtersManager.saveFiltersToSession(req, entityPath, filtersToSave);
60
+ sessionFilters = filtersToSave;
61
+ }
62
+ let mergedFilters = shouldClearFilters ? {} : Object.assign({}, sessionFilters.regular || {});
63
+ let finalEntityFilterTerm = shouldClearFilters ? '' : sessionFilters.entityFilterTerm || '';
64
+ let filters = this.filtersManager.prepareFilters(mergedFilters, driverResource);
65
+ let textFilters = this.filtersManager.prepareTextFilters(finalEntityFilterTerm, driverResource);
66
+ let combinedFilters = this.filtersManager.combineFilters(filters, textFilters);
67
+ let totalEntities = await this.countTotalEntities(driverResource, combinedFilters);
42
68
  let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
43
69
  let renderedPagination = '';
44
70
  for(let paginationItem of PageRangeProvider.fetch(currentPage, totalPages)){
71
+ let paginationUrl = this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value;
72
+ if(finalEntityFilterTerm){
73
+ paginationUrl += '&entityFilterTerm='+encodeURIComponent(finalEntityFilterTerm);
74
+ }
75
+ for(let filterKey of Object.keys(mergedFilters)){
76
+ if(mergedFilters[filterKey]){
77
+ paginationUrl += '&filters['+filterKey+']='+encodeURIComponent(mergedFilters[filterKey]);
78
+ }
79
+ }
45
80
  renderedPagination += await this.adminContentsRender(
46
81
  this.adminFilesContents.fields.view['link'],
47
82
  {
48
83
  fieldName: paginationItem.label,
49
- fieldValue: this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value,
84
+ fieldValue: paginationUrl,
50
85
  fieldOriginalValue: paginationItem.value,
51
86
  }
52
87
  );
53
88
  }
54
89
  let listProperties = {
55
- entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath
90
+ entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
91
+ entityFilterTermValue: finalEntityFilterTerm || ''
56
92
  };
57
93
  await this.emitEvent('reldens.adminListPropertiesPopulation', {
58
94
  req,
@@ -73,12 +109,13 @@ class RouterContents
73
109
  );
74
110
  return {name: property, value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle};
75
111
  }),
76
- rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, filters)
112
+ rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, combinedFilters)
77
113
  }),
78
114
  pagination: renderedPagination,
79
- extraContentForList: sc.get(listProperties, 'extraContentForList', '')
115
+ extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
116
+ entityFilterTermValue: listProperties.entityFilterTermValue
80
117
  }, ...driverResource.options.filterProperties.map((property) => {
81
- let filterValue = (filtersFromParams[property] || '');
118
+ let filterValue = (mergedFilters[property] || '');
82
119
  return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
83
120
  }))
84
121
  ),
@@ -237,9 +274,13 @@ class RouterContents
237
274
  }
238
275
  }
239
276
  }
240
- if('saveAndContinue' === sc.get(req.body, 'saveAction', 'save')){
277
+ let saveAction = sc.get(req.body, 'saveAction', 'save');
278
+ if('saveAndContinue' === saveAction){
241
279
  return this.generateEntityRoute('editPath', driverResource, idProperty, saveResult) +'&result=success';
242
280
  }
281
+ if('saveAndGoBack' === saveAction){
282
+ return this.rootPath+'/'+entityPath+'?result=success';
283
+ }
243
284
  return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
244
285
  } catch (error) {
245
286
  Logger.error('Save entity error.', error);
@@ -420,7 +461,7 @@ class RouterContents
420
461
  }
421
462
  }
422
463
  return await this.adminContentsRender(
423
- this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType)],
464
+ this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType, fieldValue)],
424
465
  {fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
425
466
  );
426
467
  }
@@ -428,7 +469,7 @@ class RouterContents
428
469
  generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
429
470
  {
430
471
  let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
431
- if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
472
+ if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
432
473
  fieldValue = sc.toJsonString(fieldValue);
433
474
  }
434
475
  let fieldName = propertyKey;
@@ -471,7 +512,7 @@ class RouterContents
471
512
  if('null' === fieldValue){
472
513
  fieldValue = '';
473
514
  }
474
- if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
515
+ if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
475
516
  fieldValue = sc.toJsonString(fieldValue);
476
517
  }
477
518
  if('boolean' === resourceProperty.type){
@@ -521,7 +562,7 @@ class RouterContents
521
562
  return await relationEntityRepository.loadAll();
522
563
  }
523
564
 
524
- propertyType(resourceProperty, templateType)
565
+ propertyType(resourceProperty, templateType, fieldValue)
525
566
  {
526
567
  let propertyType = sc.get(resourceProperty, 'type', 'text');
527
568
  if('reference' === propertyType && 'edit' === templateType){
@@ -532,11 +573,18 @@ class RouterContents
532
573
  return 'file';
533
574
  }
534
575
  if('view' === templateType){
576
+ if(!fieldValue || '' === fieldValue){
577
+ return 'text';
578
+ }
535
579
  let multiple = resourceProperty.isArray ? 's' : '';
536
- if('image' === resourceProperty.allowedTypes){
537
- return resourceProperty.allowedTypes + multiple;
580
+ let allowedTypes = sc.get(resourceProperty, 'allowedTypes', '');
581
+ if('' !== allowedTypes){
582
+ let templateName = allowedTypes + multiple;
583
+ if(sc.hasOwn(this.adminFilesContents.fields.view, templateName)){
584
+ return templateName;
585
+ }
538
586
  }
539
- if('text' === resourceProperty.allowedTypes){
587
+ if('text' === allowedTypes){
540
588
  return 'link'+multiple
541
589
  }
542
590
  return 'text';
@@ -577,42 +625,6 @@ class RouterContents
577
625
  return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
578
626
  }
579
627
 
580
- prepareFilters(filtersList, driverResource)
581
- {
582
- if(0 === Object.keys(filtersList).length){
583
- return {};
584
- }
585
- let filters = {};
586
- for(let i of Object.keys(filtersList)){
587
- let filter = filtersList[i];
588
- if('' === filter || null === filter || undefined === filter){
589
- continue;
590
- }
591
- let rawConfigFilterProperties = driverResource.options.properties[i];
592
- if(!rawConfigFilterProperties){
593
- Logger.critical('Could not found property by key.', i);
594
- continue;
595
- }
596
- if(rawConfigFilterProperties.isUpload){
597
- continue;
598
- }
599
- if('reference' === rawConfigFilterProperties.type){
600
- filters[i] = Number(filter);
601
- continue;
602
- }
603
- if('boolean' === rawConfigFilterProperties.type){
604
- filters[i] = ('true' === filter || '1' === filter || 1 === filter);
605
- continue;
606
- }
607
- if('number' === rawConfigFilterProperties.type || rawConfigFilterProperties.isId){
608
- filters[i] = Number(filter);
609
- continue;
610
- }
611
- filters[i] = {operator: 'LIKE', value: '%'+filter+'%'};
612
- }
613
- return filters;
614
- }
615
-
616
628
  }
617
629
 
618
630
  module.exports.RouterContents = RouterContents;
@@ -37,6 +37,7 @@ class Router
37
37
  this.generateEditRouteContent = props.generateEditRouteContent;
38
38
  this.processDeleteEntities = props.processDeleteEntities;
39
39
  this.processSaveEntity = props.processSaveEntity;
40
+ this.checkAndReloadAdminTemplates = props.checkAndReloadAdminTemplates;
40
41
  this.setupAdminRouter();
41
42
  }
42
43
 
@@ -61,12 +62,22 @@ class Router
61
62
  return true;
62
63
  }
63
64
 
65
+ async reloadTemplatesIfNeeded()
66
+ {
67
+ if(!this.checkAndReloadAdminTemplates){
68
+ return false;
69
+ }
70
+ return await this.checkAndReloadAdminTemplates();
71
+ }
72
+
64
73
  setupAdminRoutes()
65
74
  {
66
75
  this.adminRouter.get(this.loginPath, async (req, res) => {
76
+ await this.reloadTemplatesIfNeeded();
67
77
  return res.send(this.adminContents().login);
68
78
  });
69
79
  this.adminRouter.post(this.loginPath, async (req, res) => {
80
+ //await this.reloadTemplatesIfNeeded();
70
81
  let { email, password } = req.body;
71
82
  let loginResult = await this.authenticationCallback(email, password, this.adminRoleId);
72
83
  if(loginResult){
@@ -76,6 +87,7 @@ class Router
76
87
  return res.redirect(this.rootPath+this.loginPath+'?login-error=true');
77
88
  });
78
89
  this.adminRouter.get('/', this.isAuthenticated.bind(this), async (req, res) => {
90
+ await this.reloadTemplatesIfNeeded();
79
91
  return res.send(this.adminContents().dashboard);
80
92
  });
81
93
  this.adminRouter.get(this.logoutPath, (req, res) => {
@@ -95,12 +107,15 @@ class Router
95
107
  let entityPath = driverResource.entityPath;
96
108
  let entityRoute = '/'+entityPath;
97
109
  this.adminRouter.get(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
110
+ await this.reloadTemplatesIfNeeded();
98
111
  return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
99
112
  });
100
113
  this.adminRouter.post(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
114
+ await this.reloadTemplatesIfNeeded();
101
115
  return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
102
116
  });
103
117
  this.adminRouter.get(entityRoute+this.viewPath, this.isAuthenticated.bind(this), async (req, res) => {
118
+ await this.reloadTemplatesIfNeeded();
104
119
  let routeContents = await this.generateViewRouteContent(req, driverResource, entityPath);
105
120
  if('' === routeContents){
106
121
  return res.redirect(this.rootPath+'/'+entityPath+'?result=errorView');
@@ -108,6 +123,7 @@ class Router
108
123
  return res.send(routeContents);
109
124
  });
110
125
  this.adminRouter.get(entityRoute+this.editPath, this.isAuthenticated.bind(this), async (req, res) => {
126
+ await this.reloadTemplatesIfNeeded();
111
127
  await this.emitEvent('reldens.adminBeforeEntityEdit', {
112
128
  req,
113
129
  res,
@@ -122,6 +138,7 @@ class Router
122
138
  });
123
139
  this.setupSavePath(entityRoute, driverResource, entityPath);
124
140
  this.adminRouter.post(entityRoute+this.deletePath, this.isAuthenticated.bind(this), async (req, res) => {
141
+ //await this.reloadTemplatesIfNeeded();
125
142
  return res.redirect(await this.processDeleteEntities(req, res, driverResource, entityPath));
126
143
  });
127
144
  await this.emitEvent('reldens.setupEntitiesRoutes', {
@@ -140,6 +157,7 @@ class Router
140
157
  entityRoute+this.savePath,
141
158
  this.isAuthenticated.bind(this),
142
159
  async (req, res) => {
160
+ //await this.reloadTemplatesIfNeeded();
143
161
  await this.emitEvent('reldens.adminBeforeEntitySave', {
144
162
  req,
145
163
  res,
@@ -168,6 +186,7 @@ class Router
168
186
  this.isAuthenticated.bind(this),
169
187
  this.uploaderFactory.createUploader(fields, this.buckets, allowedFileTypes),
170
188
  async (req, res) => {
189
+ //await this.reloadTemplatesIfNeeded();
171
190
  await this.emitEvent('reldens.adminBeforeEntitySave', {
172
191
  req,
173
192
  res,