@reldens/cms 0.19.0 → 0.21.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 (55) hide show
  1. package/README.md +399 -17
  2. package/admin/reldens-admin-client.css +156 -99
  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/layout.html +15 -9
  7. package/admin/templates/list-content.html +4 -2
  8. package/admin/templates/list.html +24 -8
  9. package/admin/templates/view.html +21 -0
  10. package/lib/admin-manager/admin-filters-manager.js +177 -0
  11. package/lib/admin-manager/contents-builder.js +1 -0
  12. package/lib/admin-manager/default-translations.js +38 -0
  13. package/lib/admin-manager/router-contents.js +50 -45
  14. package/lib/admin-manager/router.js +19 -0
  15. package/lib/frontend/content-renderer.js +178 -0
  16. package/lib/frontend/entity-access-manager.js +63 -0
  17. package/lib/frontend/request-processor.js +128 -0
  18. package/lib/frontend/response-manager.js +54 -0
  19. package/lib/frontend/template-cache.js +102 -0
  20. package/lib/frontend/template-resolver.js +111 -0
  21. package/lib/frontend.js +111 -538
  22. package/lib/manager.js +26 -12
  23. package/lib/search-renderer.js +15 -7
  24. package/lib/search-request-handler.js +67 -0
  25. package/lib/search.js +13 -1
  26. package/lib/template-engine/asset-transformer.js +41 -0
  27. package/lib/template-engine/collections-single-transformer.js +28 -5
  28. package/lib/template-engine/collections-transformer.js +66 -32
  29. package/lib/template-engine/date-transformer.js +53 -0
  30. package/lib/template-engine/entities-transformer.js +5 -2
  31. package/lib/template-engine/partials-transformer.js +8 -5
  32. package/lib/template-engine/system-variables-provider.js +108 -0
  33. package/lib/template-engine/translate-transformer.js +98 -0
  34. package/lib/template-engine/translation-service.js +104 -0
  35. package/lib/template-engine/url-transformer.js +41 -0
  36. package/lib/template-engine.js +99 -12
  37. package/lib/template-reloader.js +307 -0
  38. package/package.json +4 -4
  39. package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
  40. package/templates/assets/favicons/default/favicon.ico +0 -0
  41. package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
  42. package/templates/js/functions.js +144 -0
  43. package/templates/js/scripts.js +5 -0
  44. package/templates/page.html +11 -5
  45. package/templates/partials/pagedCollection.html +1 -1
  46. package/lib/admin-translations.js +0 -56
  47. package/templates/favicon.ico +0 -0
  48. /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
  49. /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
  50. /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
  51. /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
  52. /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
  53. /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
  54. /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
  55. /package/templates/assets/favicons/{safari-pinned-tab.svg → default/safari-pinned-tab.svg} +0 -0
@@ -12,42 +12,6 @@ window.addEventListener('DOMContentLoaded', () => {
12
12
  let queryString = location.search;
13
13
  let urlParams = new URLSearchParams(queryString);
14
14
 
15
- function getCookie(name)
16
- {
17
- let value = `; ${document.cookie}`;
18
- let parts = value.split(`; ${name}=`);
19
- if(2 === parts.length){
20
- return parts.pop().split(';').shift()
21
- }
22
- }
23
-
24
- function deleteCookie(name)
25
- {
26
- document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
27
- }
28
-
29
- function escapeHTML(str)
30
- {
31
- return str.replace(/&/g, "&")
32
- .replace(/</g, "&lt;")
33
- .replace(/>/g, "&gt;")
34
- .replace(/"/g, "&quot;")
35
- .replace(/'/g, "&#039;");
36
- }
37
-
38
- function cloneElement(element)
39
- {
40
- if(element instanceof HTMLCanvasElement){
41
- let clonedCanvas = document.createElement('canvas');
42
- clonedCanvas.width = element.width;
43
- clonedCanvas.height = element.height;
44
- let ctx = clonedCanvas.getContext('2d');
45
- ctx.drawImage(element, 0, 0);
46
- return clonedCanvas
47
- }
48
- return element.cloneNode(true);
49
- }
50
-
51
15
  // error codes messages map:
52
16
  let errorMessages = {
53
17
  saveBadPatchData: 'Bad patch data on update.',
@@ -59,56 +23,9 @@ window.addEventListener('DOMContentLoaded', () => {
59
23
  errorId: 'Missing entity ID on POST.'
60
24
  };
61
25
 
62
- // copyright year:
63
- let copyRightYear = document.querySelector('.copyright-year');
64
- if(copyRightYear){
65
- copyRightYear.innerHTML = String((new Date()).getFullYear());
66
- }
67
-
68
- // activate expand/collapse elements
69
- let expandCollapseButtons = document.querySelectorAll('[data-expand-collapse]');
70
- if(expandCollapseButtons){
71
- for(let expandCollapseButton of expandCollapseButtons){
72
- expandCollapseButton.addEventListener('click', (event) => {
73
- let expandCollapseElement = document.querySelector(event.currentTarget.dataset.expandCollapse);
74
- if(expandCollapseElement){
75
- expandCollapseElement.classList.toggle('hidden');
76
- }
77
- });
78
- }
79
- }
26
+ activateExpandCollapse();
80
27
 
81
- // activate modals on click
82
- let modalElements = document.querySelectorAll('[data-toggle="modal"]');
83
- if(modalElements){
84
- for(let modalElement of modalElements){
85
- modalElement.addEventListener('click', () => {
86
- let overlay = document.createElement('div');
87
- overlay.classList.add('modal-overlay');
88
- let modal = document.createElement('div');
89
- modal.classList.add('modal');
90
- modal.classList.add('clickable');
91
- let clonedElement = cloneElement(modalElement);
92
- clonedElement.classList.add('clickable');
93
- modal.appendChild(clonedElement);
94
- overlay.appendChild(modal);
95
- document.body.appendChild(overlay);
96
- clonedElement.addEventListener('click', () => {
97
- document.body.removeChild(overlay);
98
- });
99
- modal.addEventListener('click', (e) => {
100
- if(e.target === modal){
101
- document.body.removeChild(modal.parentNode);
102
- }
103
- });
104
- overlay.addEventListener('click', (e) => {
105
- if(e.target === overlay) {
106
- document.body.removeChild(overlay);
107
- }
108
- });
109
- });
110
- }
111
- }
28
+ activateModalElements();
112
29
 
113
30
  // login errors:
114
31
  if('true' === urlParams.get('login-error')){
@@ -118,23 +35,64 @@ window.addEventListener('DOMContentLoaded', () => {
118
35
  }
119
36
  }
120
37
 
38
+ // entity search functionality:
39
+ let entityFilterTerm = document.querySelector('#entityFilterTerm');
40
+ let filterForm = document.querySelector('#filter-form');
41
+ let allFilters = document.querySelectorAll('.filters-toggle-content .filter input');
42
+ if(entityFilterTerm && filterForm){
43
+ entityFilterTerm.addEventListener('input', () => {
44
+ if(entityFilterTerm.value){
45
+ for(let filterInput of allFilters){
46
+ filterInput.value = '';
47
+ }
48
+ }
49
+ });
50
+ entityFilterTerm.addEventListener('keypress', (event) => {
51
+ if(13 === event.keyCode){
52
+ event.preventDefault();
53
+ filterForm.submit();
54
+ }
55
+ });
56
+ for(let filterInput of allFilters){
57
+ filterInput.addEventListener('input', () => {
58
+ if(filterInput.value){
59
+ entityFilterTerm.value = '';
60
+ }
61
+ });
62
+ }
63
+ filterForm.addEventListener('submit', () => {
64
+ if(entityFilterTerm.value && allFilters.some(input => input.value)){
65
+ for(let filterInput of allFilters){
66
+ filterInput.value = '';
67
+ }
68
+ }
69
+ });
70
+ }
71
+
121
72
  // forms behavior:
122
73
  let forms = document.querySelectorAll('form');
123
74
  if(forms){
124
75
  for(let form of forms){
125
76
  form.addEventListener('submit', (event) => {
126
- let submitButton = document.querySelector('input[type="submit"]');
77
+ let submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
127
78
  submitButton.disabled = true;
128
79
  let loadingImage = document.querySelector('.submit-container .loading');
129
80
  if(loadingImage){
130
81
  loadingImage.classList.remove('hidden');
131
82
  }
132
83
  if(form.classList.contains('form-delete') || form.classList.contains('confirmation-required')){
133
- if(!confirm('Are you sure?')){
134
- event.preventDefault();
135
- submitButton.disabled = false;
136
- loadingImage.classList.add('hidden');
137
- }
84
+ event.preventDefault();
85
+ showConfirmDialog((confirmed) => {
86
+ if(confirmed){
87
+ form.submit();
88
+ }
89
+ if(!confirmed){
90
+ submitButton.disabled = false;
91
+ if(loadingImage){
92
+ loadingImage.classList.add('hidden');
93
+ }
94
+ }
95
+ });
138
96
  }
139
97
  });
140
98
  }
@@ -180,21 +138,33 @@ window.addEventListener('DOMContentLoaded', () => {
180
138
  filtersToggleContent.classList.toggle('hidden');
181
139
  });
182
140
  let allFilters = document.querySelectorAll('.filters-toggle-content .filter input');
141
+ let entitySearchInput = document.querySelector('#entityFilterTerm');
142
+ let hasEntitySearch = entitySearchInput && '' !== entitySearchInput.value;
183
143
  let activeFilters = Array.from(allFilters).filter(input => '' !== input.value);
184
- if(0 < activeFilters.length){
144
+ if(0 < activeFilters.length || hasEntitySearch){
185
145
  filtersToggleContent.classList.remove('hidden');
186
- let paginationLinks = document.querySelectorAll('.pagination a');
187
- let filtersForm = document.querySelector('#filter-form');
188
- if(paginationLinks && filtersForm){
189
- for(let link of paginationLinks){
190
- link.addEventListener('click', (event) => {
191
- event.stopPropagation();
192
- event.preventDefault();
193
- filtersForm.action = link.href;
194
- filtersForm.submit();
195
- return false;
196
- })
197
- }
146
+ }
147
+ let paginationLinks = document.querySelectorAll('.pagination a');
148
+ if(paginationLinks && filterForm){
149
+ for(let link of paginationLinks){
150
+ link.addEventListener('click', (event) => {
151
+ event.stopPropagation();
152
+ event.preventDefault();
153
+ let url = new URL(link.href);
154
+ let params = new URLSearchParams(url.search);
155
+ if(entitySearchInput && entitySearchInput.value){
156
+ params.set('entityFilterTerm', entitySearchInput.value);
157
+ }
158
+ for(let filterInput of allFilters){
159
+ if(filterInput.value){
160
+ let filterName = filterInput.name;
161
+ params.set(filterName, filterInput.value);
162
+ }
163
+ }
164
+ let newUrl = url.pathname + '?' + params;
165
+ window.location.href = newUrl;
166
+ return false;
167
+ })
198
168
  }
199
169
  }
200
170
  }
@@ -216,19 +186,31 @@ window.addEventListener('DOMContentLoaded', () => {
216
186
  let deleteSelectionForm = document.getElementById('delete-selection-form');
217
187
  let hiddenInput = document.querySelector('.hidden-ids-input');
218
188
  if(listDeleteSelection && deleteSelectionForm && hiddenInput){
219
- listDeleteSelection.addEventListener('click', () => {
220
- if(!confirm('Are you sure?')){
221
- return;
222
- }
223
- let checkboxes = document.querySelectorAll('.ids-checkbox');
224
- let ids = [];
225
- for(let checkbox of checkboxes){
226
- if(checkbox.checked){
227
- ids.push(checkbox.value);
189
+ listDeleteSelection.addEventListener('click', (event) => {
190
+ event.preventDefault();
191
+ showConfirmDialog((confirmed) => {
192
+ if(confirmed){
193
+ let checkboxes = document.querySelectorAll('.ids-checkbox');
194
+ let ids = [];
195
+ for(let checkbox of checkboxes){
196
+ if(checkbox.checked){
197
+ ids.push(parseInt(checkbox.value));
198
+ }
199
+ }
200
+ if(0 === ids.length){
201
+ return;
202
+ }
203
+ deleteSelectionForm.innerHTML = '';
204
+ for(let id of ids){
205
+ let input = document.createElement('input');
206
+ input.type = 'hidden';
207
+ input.name = 'ids[]';
208
+ input.value = id;
209
+ deleteSelectionForm.appendChild(input);
210
+ }
211
+ deleteSelectionForm.submit();
228
212
  }
229
- }
230
- hiddenInput.value = ids.join(',');
231
- deleteSelectionForm.submit();
213
+ });
232
214
  });
233
215
  }
234
216
 
@@ -278,25 +260,18 @@ window.addEventListener('DOMContentLoaded', () => {
278
260
 
279
261
  // cache clear all functionality:
280
262
  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
263
  let cacheClearForm = document.querySelector('.cache-clear-form');
284
- if(cacheClearAllButton && cacheConfirmDialog){
264
+ if(cacheClearAllButton){
285
265
  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
- }
266
+ showConfirmDialog((confirmed) => {
267
+ if(confirmed && cacheClearForm){
268
+ let submitButton = cacheClearForm.querySelector('button[type="submit"]');
269
+ if(submitButton){
270
+ submitButton.disabled = true;
271
+ }
272
+ cacheClearForm.submit();
273
+ }
274
+ });
300
275
  });
301
276
  }
302
277
 
@@ -1,17 +1,17 @@
1
1
  <button type="button" class="button button-warning cache-clear-all-button">
2
2
  {{buttonText}}
3
3
  </button>
4
- <dialog class="cache-confirm-dialog">
5
- <div class="cache-dialog-content">
6
- <h5>{{confirmTitle}}</h5>
7
- <p>{{confirmMessage}}</p>
4
+ <dialog class="confirm-dialog">
5
+ <div class="dialog-content">
6
+ <h5 class="dialog-title">{{confirmTitle}}</h5>
7
+ <p class="dialog-message">{{confirmMessage}}</p>
8
8
  <div class="alert cache-warning">
9
9
  <strong>{{warningText}}</strong> {{warningMessage}}
10
10
  </div>
11
- <div class="cache-dialog-actions">
12
- <button type="button" class="button button-secondary cache-dialog-cancel">{{cancelText}}</button>
11
+ <div class="dialog-actions">
12
+ <button type="button" class="button button-secondary dialog-cancel">{{cancelText}}</button>
13
13
  <form method="post" action="{{clearAllCacheRoute}}" class="cache-clear-form">
14
- <button type="submit" class="button button-danger">{{confirmText}}</button>
14
+ <button type="submit" class="button button-danger dialog-confirm">{{confirmText}}</button>
15
15
  </form>
16
16
  </div>
17
17
  </div>
@@ -1,5 +1,11 @@
1
1
  <div class="entity-edit {{&entityName}}-edit">
2
2
  <h2>{{&templateTitle}}</h2>
3
+ <div class="actions">
4
+ <button class="button button-primary button-submit" type="submit" form="edit-form" name="saveAction" value="save">Save</button>
5
+ <button class="button button-primary button-submit" type="submit" form="edit-form" name="saveAction" value="saveAndContinue">Save and continue...</button>
6
+ <button class="button button-primary button-submit" type="submit" form="edit-form" name="saveAction" value="saveAndGoBack">Save and go back</button>
7
+ <a class="button button-secondary button-back" href="{{&entityViewRoute}}">Cancel</a>
8
+ </div>
3
9
  <form name="edit-form" id="edit-form" action="{{&entitySaveRoute}}" method="post"{{&multipartFormData}}>
4
10
  <input type="hidden" name="{{&idProperty}}" value="{{&idValue}}"/>
5
11
  <div class="edit-field">
@@ -18,6 +24,7 @@
18
24
  <div class="actions">
19
25
  <button class="button button-primary button-submit" type="submit" name="saveAction" value="save">Save</button>
20
26
  <button class="button button-primary button-submit" type="submit" name="saveAction" value="saveAndContinue">Save and continue...</button>
27
+ <button class="button button-primary button-submit" type="submit" name="saveAction" value="saveAndGoBack">Save and go back</button>
21
28
  <a class="button button-secondary button-back" href="{{&entityViewRoute}}">Cancel</a>
22
29
  </div>
23
30
  </form>
@@ -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>
@@ -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}}',