@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.
- package/README.md +399 -17
- package/admin/reldens-admin-client.css +156 -99
- package/admin/reldens-admin-client.js +108 -133
- package/admin/templates/clear-all-cache-button.html +7 -7
- package/admin/templates/edit.html +7 -0
- package/admin/templates/layout.html +15 -9
- package/admin/templates/list-content.html +4 -2
- package/admin/templates/list.html +24 -8
- package/admin/templates/view.html +21 -0
- package/lib/admin-manager/admin-filters-manager.js +177 -0
- package/lib/admin-manager/contents-builder.js +1 -0
- package/lib/admin-manager/default-translations.js +38 -0
- package/lib/admin-manager/router-contents.js +50 -45
- package/lib/admin-manager/router.js +19 -0
- package/lib/frontend/content-renderer.js +178 -0
- package/lib/frontend/entity-access-manager.js +63 -0
- package/lib/frontend/request-processor.js +128 -0
- package/lib/frontend/response-manager.js +54 -0
- package/lib/frontend/template-cache.js +102 -0
- package/lib/frontend/template-resolver.js +111 -0
- package/lib/frontend.js +111 -538
- package/lib/manager.js +26 -12
- package/lib/search-renderer.js +15 -7
- package/lib/search-request-handler.js +67 -0
- package/lib/search.js +13 -1
- package/lib/template-engine/asset-transformer.js +41 -0
- package/lib/template-engine/collections-single-transformer.js +28 -5
- package/lib/template-engine/collections-transformer.js +66 -32
- package/lib/template-engine/date-transformer.js +53 -0
- package/lib/template-engine/entities-transformer.js +5 -2
- package/lib/template-engine/partials-transformer.js +8 -5
- package/lib/template-engine/system-variables-provider.js +108 -0
- package/lib/template-engine/translate-transformer.js +98 -0
- package/lib/template-engine/translation-service.js +104 -0
- package/lib/template-engine/url-transformer.js +41 -0
- package/lib/template-engine.js +99 -12
- package/lib/template-reloader.js +307 -0
- package/package.json +4 -4
- package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
- package/templates/assets/favicons/default/favicon.ico +0 -0
- package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
- package/templates/js/functions.js +144 -0
- package/templates/js/scripts.js +5 -0
- package/templates/page.html +11 -5
- package/templates/partials/pagedCollection.html +1 -1
- package/lib/admin-translations.js +0 -56
- package/templates/favicon.ico +0 -0
- /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
- /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
- /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
- /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
- /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
- /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
- /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
- /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, "<")
|
|
33
|
-
.replace(/>/g, ">")
|
|
34
|
-
.replace(/"/g, """)
|
|
35
|
-
.replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
264
|
+
if(cacheClearAllButton){
|
|
285
265
|
cacheClearAllButton.addEventListener('click', () => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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="
|
|
5
|
-
<div class="
|
|
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="
|
|
12
|
-
<button type="button" class="button button-secondary
|
|
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
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
<link rel="
|
|
11
|
-
<
|
|
12
|
-
<link rel="
|
|
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
|
|
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
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
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;
|