@gov-cy/govcy-express-services 1.0.0-alpha.10 → 1.0.0-alpha.11
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/package.json +6 -3
- package/src/middleware/govcyFileDeleteHandler.mjs +2 -1
- package/src/middleware/govcyPageHandler.mjs +1 -5
- package/src/middleware/govcyPageRender.mjs +10 -0
- package/src/middleware/govcyReviewPageHandler.mjs +4 -1
- package/src/middleware/govcySuccessPageHandler.mjs +1 -2
- package/src/public/js/govcyFiles.js +102 -0
- package/src/resources/govcyResources.mjs +13 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gov-cy/govcy-express-services",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
4
4
|
"description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
|
|
5
5
|
"author": "DMRID - DSF Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,11 +44,13 @@
|
|
|
44
44
|
"test:integration": "mocha --recursive tests/integration/**/*.test.mjs",
|
|
45
45
|
"test:package": "mocha --recursive tests/package/**/*.test.mjs",
|
|
46
46
|
"test:functional": "mocha --timeout 30000 --recursive tests/functional/**/*.test.mjs",
|
|
47
|
-
"test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs"
|
|
47
|
+
"test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs",
|
|
48
|
+
"coverage": "c8 --reporter=html --reporter=text --reporter=lcov npm test",
|
|
49
|
+
"coverage:report": "c8 report"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
52
|
"@gov-cy/dsf-email-templates": "^2.1.0",
|
|
51
|
-
"@gov-cy/govcy-frontend-renderer": "^1.
|
|
53
|
+
"@gov-cy/govcy-frontend-renderer": "^1.22.0",
|
|
52
54
|
"axios": "^1.9.0",
|
|
53
55
|
"cookie-parser": "^1.4.7",
|
|
54
56
|
"dotenv": "^16.3.1",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"puppeteer": "^24.6.0"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
65
|
+
"c8": "^10.1.3",
|
|
63
66
|
"chai": "^5.2.0",
|
|
64
67
|
"chai-http": "^5.1.1",
|
|
65
68
|
"mocha": "^11.1.0",
|
|
@@ -139,7 +139,7 @@ export function govcyFileDeletePageHandler() {
|
|
|
139
139
|
//--------- End Handle Validation Errors ---------
|
|
140
140
|
|
|
141
141
|
// Add elements to the main section, the H1, summary list, the submit button and the JS
|
|
142
|
-
mainElements.push(formElement
|
|
142
|
+
mainElements.push(formElement);
|
|
143
143
|
// Append generated summary list to the page template
|
|
144
144
|
pageTemplate.sections.push({ name: "main", elements: mainElements });
|
|
145
145
|
|
|
@@ -221,6 +221,7 @@ export function govcyFileDeletePostHandler() {
|
|
|
221
221
|
//if no validation errors
|
|
222
222
|
if (req.body.deleteFile === "yes") {
|
|
223
223
|
dataLayer.storePageDataElement(req.session, siteId, pageUrl, elementName, "");
|
|
224
|
+
logger.info(`File deleted by user`, { siteId, pageUrl, elementName });
|
|
224
225
|
}
|
|
225
226
|
// construct the page url
|
|
226
227
|
let myUrl = new URL(pageBaseReturnUrl);
|
|
@@ -53,11 +53,7 @@ export function govcyPageHandler() {
|
|
|
53
53
|
element.params.method = "POST";
|
|
54
54
|
// ➕ Add CSRF token
|
|
55
55
|
element.params.elements.push(govcyResources.csrfTokenInput(req.csrfToken()));
|
|
56
|
-
|
|
57
|
-
// element.params.elements.push(govcyResources.siteAndPageInput(siteId, pageUrl, req.globalLang));
|
|
58
|
-
// ➕ Add govcyFormsJs script to the form
|
|
59
|
-
element.params.elements.push(govcyResources.staticResources.elements["govcyFormsJs"]);
|
|
60
|
-
|
|
56
|
+
|
|
61
57
|
// 🔍 Find the first button with `prototypeNavigate`
|
|
62
58
|
const button = element.params.elements.find(subElement =>
|
|
63
59
|
// subElement.element === "button" && subElement.params.prototypeNavigate
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
|
|
2
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Middleware function to render pages using the GovCy Frontend Renderer.
|
|
@@ -6,8 +7,17 @@ import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
|
|
|
6
7
|
*/
|
|
7
8
|
export function renderGovcyPage() {
|
|
8
9
|
return (req, res) => {
|
|
10
|
+
const afterBody = {
|
|
11
|
+
name: "afterBody",
|
|
12
|
+
elements: [
|
|
13
|
+
govcyResources.staticResources.elements["govcyLoadingOverlay"],
|
|
14
|
+
govcyResources.staticResources.elements["govcyFormsJs"]
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
// Initialize the renderer
|
|
9
18
|
const renderer = new govcyFrontendRenderer();
|
|
10
19
|
const { processedPage } = req;
|
|
20
|
+
processedPage.pageTemplate.sections.push(afterBody);
|
|
11
21
|
const html = renderer.renderFromJSON(processedPage.pageTemplate, processedPage.pageData);
|
|
12
22
|
res.send(html);
|
|
13
23
|
};
|
|
@@ -79,7 +79,10 @@ export function govcyReviewPageHandler() {
|
|
|
79
79
|
//--------- End Handle Validation Errors ---------
|
|
80
80
|
|
|
81
81
|
// Add elements to the main section, the H1, summary list, the submit button and the JS
|
|
82
|
-
mainElements.push(pageH1,
|
|
82
|
+
mainElements.push(pageH1,
|
|
83
|
+
summaryList,
|
|
84
|
+
submitButton
|
|
85
|
+
);
|
|
83
86
|
// Append generated summary list to the page template
|
|
84
87
|
pageTemplate.sections.push({ name: "main", elements: mainElements });
|
|
85
88
|
|
|
@@ -87,8 +87,7 @@ export function govcySuccessPageHandler(isPDF = false) {
|
|
|
87
87
|
weHaveSendYouAnEmail,
|
|
88
88
|
pdfLink,
|
|
89
89
|
theDataFromYourRequest,
|
|
90
|
-
summaryList
|
|
91
|
-
govcyResources.staticResources.elements["govcyFormsJs"]
|
|
90
|
+
summaryList
|
|
92
91
|
);
|
|
93
92
|
// Append generated summary list to the page template
|
|
94
93
|
pageTemplate.sections.push({ name: "main", elements: mainElements });
|
|
@@ -1,11 +1,105 @@
|
|
|
1
1
|
// 🔍 Select all file inputs that have the .govcy-file-upload class
|
|
2
2
|
var fileInputs = document.querySelectorAll('input[type="file"].govcy-file-upload');
|
|
3
3
|
|
|
4
|
+
// select overlay and app root elements
|
|
5
|
+
var _govcyOverlay = document.getElementById("govcy--loadingOverlay");
|
|
6
|
+
var _govcyAppRoot = document.getElementById("govcy--body");
|
|
7
|
+
|
|
8
|
+
// Accessibility: Keep track of previously focused element and disabled elements
|
|
9
|
+
var _govcyPrevFocus = null;
|
|
10
|
+
var _govcyDisabledEls = [];
|
|
11
|
+
|
|
4
12
|
// 🔁 Loop over each file input and attach a change event listener
|
|
5
13
|
fileInputs.forEach(function(input) {
|
|
6
14
|
input.addEventListener('change', _uploadFileEventHandler);
|
|
7
15
|
});
|
|
8
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Disables all focusable elements within a given root element
|
|
19
|
+
* @param {*} root The root element whose focusable children will be disabled
|
|
20
|
+
*/
|
|
21
|
+
function disableFocusables(root) {
|
|
22
|
+
var sel = 'a[href],area[href],button,input,select,textarea,iframe,summary,[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
|
|
23
|
+
var nodes = root.querySelectorAll(sel);
|
|
24
|
+
_govcyDisabledEls = [];
|
|
25
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
26
|
+
var el = nodes[i];
|
|
27
|
+
if (_govcyOverlay.contains(el)) continue; // don’t disable overlay itself
|
|
28
|
+
var prev = el.getAttribute('tabindex');
|
|
29
|
+
el.setAttribute('data-prev-tabindex', prev === null ? '' : prev);
|
|
30
|
+
el.setAttribute('tabindex', '-1');
|
|
31
|
+
_govcyDisabledEls.push(el);
|
|
32
|
+
}
|
|
33
|
+
root.setAttribute('aria-hidden', 'true'); // hide from AT on fallback
|
|
34
|
+
root.setAttribute('aria-busy', 'true');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Restores all focusable elements within a given root element
|
|
39
|
+
* @param {*} root The root element whose focusable children will be restored
|
|
40
|
+
*/
|
|
41
|
+
function restoreFocusables(root) {
|
|
42
|
+
for (var i = 0; i < _govcyDisabledEls.length; i++) {
|
|
43
|
+
var el = _govcyDisabledEls[i];
|
|
44
|
+
var prev = el.getAttribute('data-prev-tabindex');
|
|
45
|
+
if (prev === '') el.removeAttribute('tabindex'); else el.setAttribute('tabindex', prev);
|
|
46
|
+
el.removeAttribute('data-prev-tabindex');
|
|
47
|
+
}
|
|
48
|
+
_govcyDisabledEls = [];
|
|
49
|
+
root.removeAttribute('aria-hidden');
|
|
50
|
+
root.removeAttribute('aria-busy');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Traps tab key navigation within the overlay
|
|
55
|
+
* @param {*} e The event
|
|
56
|
+
* @returns
|
|
57
|
+
*/
|
|
58
|
+
function trapTab(e) {
|
|
59
|
+
if (e.key !== 'Tab') return;
|
|
60
|
+
var focusables = _govcyOverlay.querySelectorAll('a[href],button,input,select,textarea,[tabindex]:not([tabindex="-1"])');
|
|
61
|
+
if (focusables.length === 0) { e.preventDefault(); _govcyOverlay.focus(); return; }
|
|
62
|
+
var first = focusables[0], last = focusables[focusables.length - 1];
|
|
63
|
+
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
|
|
64
|
+
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Shows the loading spinner overlay and traps focus within it
|
|
69
|
+
*/
|
|
70
|
+
function showLoadingSpinner() {
|
|
71
|
+
_govcyPrevFocus = document.activeElement;
|
|
72
|
+
_govcyOverlay.setAttribute('aria-hidden', 'false');
|
|
73
|
+
_govcyOverlay.setAttribute('tabindex', '-1');
|
|
74
|
+
_govcyOverlay.style.display = 'flex';
|
|
75
|
+
document.documentElement.style.overflow = 'hidden';
|
|
76
|
+
|
|
77
|
+
if ('inert' in HTMLElement.prototype) { // progressive enhancement
|
|
78
|
+
_govcyAppRoot.inert = true;
|
|
79
|
+
} else {
|
|
80
|
+
disableFocusables(_govcyAppRoot);
|
|
81
|
+
document.addEventListener('keydown', trapTab, true);
|
|
82
|
+
}
|
|
83
|
+
_govcyOverlay.focus();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hides the loading spinner overlay and restores focus to the previously focused element
|
|
88
|
+
*/
|
|
89
|
+
function hideLoadingSpinner() {
|
|
90
|
+
_govcyOverlay.style.display = 'none';
|
|
91
|
+
_govcyOverlay.setAttribute('aria-hidden', 'true');
|
|
92
|
+
document.documentElement.style.overflow = '';
|
|
93
|
+
|
|
94
|
+
if ('inert' in HTMLElement.prototype) {
|
|
95
|
+
_govcyAppRoot.inert = false;
|
|
96
|
+
} else {
|
|
97
|
+
restoreFocusables(_govcyAppRoot);
|
|
98
|
+
document.removeEventListener('keydown', trapTab, true);
|
|
99
|
+
}
|
|
100
|
+
if (_govcyPrevFocus && _govcyPrevFocus.focus) _govcyPrevFocus.focus();
|
|
101
|
+
}
|
|
102
|
+
|
|
9
103
|
|
|
10
104
|
/**
|
|
11
105
|
* Handles the upload of a file event
|
|
@@ -63,6 +157,9 @@ function _uploadFileEventHandler(event) {
|
|
|
63
157
|
|
|
64
158
|
if (!file) return; // Exit if no file was selected
|
|
65
159
|
|
|
160
|
+
// Show loading spinner
|
|
161
|
+
showLoadingSpinner();
|
|
162
|
+
|
|
66
163
|
// 🧵 Prepare form-data payload for the API
|
|
67
164
|
var formData = new FormData();
|
|
68
165
|
formData.append('file', file); // Attach the actual file
|
|
@@ -93,6 +190,9 @@ function _uploadFileEventHandler(event) {
|
|
|
93
190
|
// 📝 Store returned metadata in hidden fields if needed
|
|
94
191
|
// document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
|
|
95
192
|
// document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
|
|
193
|
+
|
|
194
|
+
// Hide loading spinner
|
|
195
|
+
hideLoadingSpinner();
|
|
96
196
|
|
|
97
197
|
// Render the file view
|
|
98
198
|
_renderFileElement("fileView", elementId, elementName, fileId, sha256, null);
|
|
@@ -117,6 +217,8 @@ function _uploadFileEventHandler(event) {
|
|
|
117
217
|
errorMessage = messages["uploadFailed" + errorCode];
|
|
118
218
|
}
|
|
119
219
|
|
|
220
|
+
// Hide loading spinner
|
|
221
|
+
hideLoadingSpinner();
|
|
120
222
|
// Render the file input with error
|
|
121
223
|
_renderFileElement("fileInput", elementId, elementName, "", "", errorMessage);
|
|
122
224
|
|
|
@@ -158,9 +158,19 @@ export const staticResources = {
|
|
|
158
158
|
element: "htmlElement",
|
|
159
159
|
params: {
|
|
160
160
|
text: {
|
|
161
|
-
en: `<script src="https://cdn.jsdelivr.net/
|
|
162
|
-
el: `<script src="https://cdn.jsdelivr.net/
|
|
163
|
-
tr: `<script src="https://cdn.jsdelivr.net/
|
|
161
|
+
en: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
|
|
162
|
+
el: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
|
|
163
|
+
tr: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
govcyLoadingOverlay: {
|
|
168
|
+
element: "htmlElement",
|
|
169
|
+
params: {
|
|
170
|
+
text: {
|
|
171
|
+
en: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Loading...</span></div></div></div>`,
|
|
172
|
+
el: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Φόρτωση...</span></div></div></div>`,
|
|
173
|
+
tr: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Loading...</span></div></div></div>`
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
},
|