@gov-cy/govcy-express-services 1.2.0 → 1.3.0-alpha.1
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 +1 -0
- package/package.json +6 -2
- package/src/index.mjs +190 -49
- package/src/middleware/govcyConfigSiteData.mjs +4 -0
- package/src/middleware/govcyFileDeleteHandler.mjs +41 -19
- package/src/middleware/govcyFileUpload.mjs +14 -1
- package/src/middleware/govcyFileViewHandler.mjs +20 -1
- package/src/middleware/govcyFormsPostHandler.mjs +76 -47
- package/src/middleware/govcyHttpErrorHandler.mjs +2 -0
- package/src/middleware/govcyMultipleThingsDeleteHandler.mjs +233 -0
- package/src/middleware/govcyMultipleThingsHubHandler.mjs +247 -0
- package/src/middleware/govcyMultipleThingsItemPage.mjs +460 -0
- package/src/middleware/govcyPageHandler.mjs +17 -5
- package/src/middleware/govcyPageRender.mjs +8 -0
- package/src/middleware/govcyReviewPageHandler.mjs +58 -32
- package/src/middleware/govcyReviewPostHandler.mjs +47 -23
- package/src/middleware/govcyRoutePageHandler.mjs +3 -1
- package/src/middleware/govcySuccessPageHandler.mjs +0 -5
- package/src/public/css/govcyExpress.css +33 -0
- package/src/public/img/Plus_24x24.svg +8 -0
- package/src/public/js/govcyFiles.js +92 -71
- package/src/resources/govcyResources.mjs +128 -0
- package/src/utils/govcyApiDetection.mjs +1 -1
- package/src/utils/govcyDataLayer.mjs +208 -67
- package/src/utils/govcyFormHandling.mjs +100 -27
- package/src/utils/govcyHandleFiles.mjs +58 -13
- package/src/utils/govcyMultipleThingsValidation.mjs +132 -0
- package/src/utils/govcySubmitData.mjs +221 -88
- package/src/utils/govcyValidator.mjs +19 -7
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
2
|
-
import { validateFormElements
|
|
2
|
+
import { validateFormElements } from "../utils/govcyValidator.mjs"; // Import your validator
|
|
3
3
|
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
4
4
|
import { logger } from "../utils/govcyLogger.mjs";
|
|
5
|
-
import {prepareSubmissionData, prepareSubmissionDataAPI, generateSubmitEmail } from "../utils/govcySubmitData.mjs";
|
|
5
|
+
import { prepareSubmissionData, prepareSubmissionDataAPI, generateSubmitEmail } from "../utils/govcySubmitData.mjs";
|
|
6
6
|
import { govcyApiRequest } from "../utils/govcyApiRequest.mjs";
|
|
7
7
|
import { getEnvVariable, getEnvVariableBool } from "../utils/govcyEnvVariables.mjs";
|
|
8
8
|
import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
|
|
9
9
|
import { sendEmail } from "../utils/govcyNotification.mjs"
|
|
10
10
|
import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
|
|
11
|
+
import { validateMultipleThings } from "../utils/govcyMultipleThingsValidation.mjs";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Middleware to handle review page form submission
|
|
@@ -17,7 +18,7 @@ export function govcyReviewPostHandler() {
|
|
|
17
18
|
return async (req, res, next) => {
|
|
18
19
|
try {
|
|
19
20
|
const { siteId } = req.params;
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
// ✅ Load service and check if it exists
|
|
22
23
|
const service = req.serviceData;
|
|
23
24
|
let validationErrors = {};
|
|
@@ -26,7 +27,7 @@ export function govcyReviewPostHandler() {
|
|
|
26
27
|
for (const page of service.pages) {
|
|
27
28
|
//get page url
|
|
28
29
|
const pageUrl = page.pageData.url;
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
// ----- Conditional logic comes here
|
|
31
32
|
// ✅ Skip validation if page is conditionally excluded
|
|
32
33
|
const conditionResult = evaluatePageConditions(page, req.session, siteId, req);
|
|
@@ -46,9 +47,32 @@ export function govcyReviewPostHandler() {
|
|
|
46
47
|
|
|
47
48
|
// Get stored form data for this page (or default to empty)
|
|
48
49
|
const formData = dataLayer.getPageData(req.session, siteId, pageUrl) || {};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
|
|
51
|
+
let errors = {};
|
|
52
|
+
// ----- MultipleThings hub handling -----
|
|
53
|
+
if (page.multipleThings) {
|
|
54
|
+
// Use multiple things validator
|
|
55
|
+
const items = Array.isArray(formData) ? formData : [];
|
|
56
|
+
const mtErrors = validateMultipleThings(page, items, service.site.lang);
|
|
57
|
+
|
|
58
|
+
if (Object.keys(mtErrors).length > 0) {
|
|
59
|
+
errors[pageUrl] = {
|
|
60
|
+
type: "multipleThings", // ✅ mark it
|
|
61
|
+
hub: { errors: mtErrors } // keep hub-style structure
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
} else { // ----- Normal form handling -----
|
|
65
|
+
// Normal page validation
|
|
66
|
+
const v = validateFormElements(formElement.params.elements, formData, pageUrl);
|
|
67
|
+
if (Object.keys(v).length > 0) {
|
|
68
|
+
errors[pageUrl] = {
|
|
69
|
+
type: "normal", // ✅ mark it
|
|
70
|
+
...v
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// // Run validations
|
|
74
|
+
// errors = validateFormElements(formElement.params.elements, formData, pageUrl);
|
|
75
|
+
}
|
|
52
76
|
|
|
53
77
|
// Add errors to the validationErrors object
|
|
54
78
|
validationErrors = { ...validationErrors, ...errors };
|
|
@@ -68,7 +92,7 @@ export function govcyReviewPostHandler() {
|
|
|
68
92
|
const clientKey = getEnvVariable(service?.site?.submissionAPIEndpoint?.clientKey || "", false);
|
|
69
93
|
const serviceId = getEnvVariable(service?.site?.submissionAPIEndpoint?.serviceId || "", false);
|
|
70
94
|
const dsfGtwApiKey = getEnvVariable(service?.site?.submissionAPIEndpoint?.dsfgtwApiKey || "", "");
|
|
71
|
-
const allowSelfSignedCerts = getEnvVariableBool("ALLOW_SELF_SIGNED_CERTIFICATES",false)
|
|
95
|
+
const allowSelfSignedCerts = getEnvVariableBool("ALLOW_SELF_SIGNED_CERTIFICATES", false); // Default to false if not set
|
|
72
96
|
if (!submissionUrl) {
|
|
73
97
|
return handleMiddlewareError("🚨 Submission API endpoint URL is missing", 500, next);
|
|
74
98
|
}
|
|
@@ -78,13 +102,13 @@ export function govcyReviewPostHandler() {
|
|
|
78
102
|
if (!serviceId) {
|
|
79
103
|
return handleMiddlewareError("🚨 Submission API serviceId is missing", 500, next);
|
|
80
104
|
}
|
|
81
|
-
|
|
105
|
+
|
|
82
106
|
// Prepare submission data
|
|
83
107
|
const submissionData = prepareSubmissionData(req, siteId, service);
|
|
84
108
|
|
|
85
109
|
// Prepare submission data for API
|
|
86
110
|
const submissionDataAPI = prepareSubmissionDataAPI(submissionData);
|
|
87
|
-
|
|
111
|
+
|
|
88
112
|
logger.debug("Prepared submission data for API:", submissionDataAPI);
|
|
89
113
|
|
|
90
114
|
// Call the API to submit the data
|
|
@@ -94,7 +118,7 @@ export function govcyReviewPostHandler() {
|
|
|
94
118
|
submissionDataAPI, // Pass the prepared submission data
|
|
95
119
|
true, // Use access token authentication
|
|
96
120
|
dataLayer.getUser(req.session), // Get the user from the session
|
|
97
|
-
{
|
|
121
|
+
{
|
|
98
122
|
accept: "text/plain", // Set Accept header to text/plain
|
|
99
123
|
"client-key": clientKey, // Set the client key header
|
|
100
124
|
"service-id": serviceId, // Set the service ID header
|
|
@@ -103,44 +127,44 @@ export function govcyReviewPostHandler() {
|
|
|
103
127
|
3,
|
|
104
128
|
allowSelfSignedCerts
|
|
105
129
|
);
|
|
106
|
-
|
|
130
|
+
|
|
107
131
|
// Check if the response is successful
|
|
108
132
|
if (response.Succeeded) {
|
|
109
133
|
let referenceNo = response?.Data?.referenceValue || "";
|
|
110
134
|
// Add the reference number to the submission data
|
|
111
|
-
submissionData.referenceNumber = referenceNo;
|
|
135
|
+
submissionData.referenceNumber = referenceNo;
|
|
112
136
|
logger.info("✅ Data submitted", siteId, referenceNo);
|
|
113
137
|
// handle data layer submission
|
|
114
138
|
dataLayer.storeSiteSubmissionData(
|
|
115
139
|
req.session,
|
|
116
|
-
siteId,
|
|
140
|
+
siteId,
|
|
117
141
|
submissionData);
|
|
118
|
-
|
|
142
|
+
|
|
119
143
|
//-- Send email to user
|
|
120
144
|
// Generate the email body
|
|
121
145
|
let emailBody = generateSubmitEmail(service, submissionData.printFriendlyData, referenceNo, req);
|
|
122
146
|
logger.debug("Email generated:", emailBody);
|
|
123
147
|
// Send the email
|
|
124
|
-
sendEmail(service.site.title[service.site.lang],emailBody,[dataLayer.getUser(req.session).email], "eMail").catch(err => {
|
|
148
|
+
sendEmail(service.site.title[service.site.lang], emailBody, [dataLayer.getUser(req.session).email], "eMail").catch(err => {
|
|
125
149
|
logger.error("Email sending failed (async):", err);
|
|
126
150
|
});
|
|
127
151
|
// --- End of email sending
|
|
128
|
-
|
|
129
|
-
logger.debug("🔄 Redirecting to success page:",
|
|
152
|
+
|
|
153
|
+
logger.debug("🔄 Redirecting to success page:", req);
|
|
130
154
|
// redirect to success
|
|
131
155
|
return res.redirect(govcyResources.constructPageUrl(siteId, `success`));
|
|
132
|
-
|
|
156
|
+
|
|
133
157
|
// logger.debug("The submission data prepared:", printFriendlyData);
|
|
134
158
|
// let reviewSummary = generateReviewSummary(printFriendlyData,req, siteId, false);
|
|
135
159
|
// res.send(emailBody);
|
|
136
|
-
|
|
160
|
+
|
|
137
161
|
// // Clear any existing submission errors from the session
|
|
138
162
|
// dataLayer.clearSiteSubmissionErrors(req.session, siteId);
|
|
139
163
|
} else {
|
|
140
164
|
// Handle submission failure
|
|
141
165
|
const errorCode = response.ErrorCode;
|
|
142
166
|
const errorPage = service.site?.submissionAPIEndpoint?.response?.errorResponse?.[errorCode]?.page;
|
|
143
|
-
|
|
167
|
+
|
|
144
168
|
if (errorPage) {
|
|
145
169
|
logger.info("🚨 Submission returned failed:", response.ErrorCode);
|
|
146
170
|
return res.redirect(errorPage);
|
|
@@ -148,7 +172,7 @@ export function govcyReviewPostHandler() {
|
|
|
148
172
|
return handleMiddlewareError("🚨 Unknown error code received from API.", 500, next);
|
|
149
173
|
}
|
|
150
174
|
}
|
|
151
|
-
|
|
175
|
+
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
// Proceed to final submission if no errors
|
|
@@ -159,5 +183,5 @@ export function govcyReviewPostHandler() {
|
|
|
159
183
|
return next(error);
|
|
160
184
|
}
|
|
161
185
|
};
|
|
162
|
-
|
|
186
|
+
|
|
163
187
|
}
|
|
@@ -45,8 +45,10 @@ export function govcyRoutePageHandler(req, res, next) {
|
|
|
45
45
|
pageData.site.lang = req.globalLang;
|
|
46
46
|
//if user is logged in add he user bane section in the page template
|
|
47
47
|
if (dataLayer.getUser(req.session)) {
|
|
48
|
-
|
|
48
|
+
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
49
49
|
}
|
|
50
|
+
// Add custom CSS path
|
|
51
|
+
pageData.site.customCSSFile = `/css/govcyExpress.css`;
|
|
50
52
|
const renderer = new govcyFrontendRenderer();
|
|
51
53
|
const html = renderer.renderFromJSON(pageTemplate, pageData);
|
|
52
54
|
res.send(html);
|
|
@@ -98,11 +98,6 @@ export function govcySuccessPageHandler(isPDF = false) {
|
|
|
98
98
|
// Append generated summary list to the page template
|
|
99
99
|
pageTemplate.sections.push({ name: "main", elements: mainElements });
|
|
100
100
|
|
|
101
|
-
//if user is logged in add he user bane section in the page template
|
|
102
|
-
if (dataLayer.getUser(req.session)) {
|
|
103
|
-
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
104
|
-
}
|
|
105
|
-
|
|
106
101
|
//prepare pageData
|
|
107
102
|
pageData.site = serviceCopy.site;
|
|
108
103
|
pageData.pageData.title = govcyResources.staticResources.text.submissionSuccessTitle;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
dl.govcy-summary-list-row-internal:not(:first-of-type) {
|
|
3
|
+
margin-top: 0.5rem !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.list-inline-item:not(:last-child) {
|
|
7
|
+
margin-right: 0 !important;
|
|
8
|
+
}
|
|
9
|
+
.list-inline-item:not(:first-child) {
|
|
10
|
+
margin-left: .5rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.govcy-add-new-item {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
text-decoration: underline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.govcy-add-new-item::before {
|
|
20
|
+
content: "";
|
|
21
|
+
display: inline-block;
|
|
22
|
+
width: 24px;
|
|
23
|
+
height: 24px;
|
|
24
|
+
background: url("/img/Plus_24x24.svg") no-repeat center center;
|
|
25
|
+
background-size: contain;
|
|
26
|
+
margin-right: 0.25rem; /* Fallback for browsers without flex gap */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (max-width: 767.98px) {
|
|
30
|
+
#multipleThingsList>tbody>tr>td {
|
|
31
|
+
padding: .5rem .5rem;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -10,7 +10,7 @@ var _govcyPrevFocus = null;
|
|
|
10
10
|
var _govcyDisabledEls = [];
|
|
11
11
|
|
|
12
12
|
// 🔁 Loop over each file input and attach a change event listener
|
|
13
|
-
fileInputs.forEach(function(input) {
|
|
13
|
+
fileInputs.forEach(function (input) {
|
|
14
14
|
input.addEventListener('change', _uploadFileEventHandler);
|
|
15
15
|
});
|
|
16
16
|
|
|
@@ -19,19 +19,19 @@ fileInputs.forEach(function(input) {
|
|
|
19
19
|
* @param {*} root The root element whose focusable children will be disabled
|
|
20
20
|
*/
|
|
21
21
|
function disableFocusables(root) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -39,15 +39,15 @@ function disableFocusables(root) {
|
|
|
39
39
|
* @param {*} root The root element whose focusable children will be restored
|
|
40
40
|
*/
|
|
41
41
|
function restoreFocusables(root) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -56,48 +56,48 @@ function restoreFocusables(root) {
|
|
|
56
56
|
* @returns
|
|
57
57
|
*/
|
|
58
58
|
function trapTab(e) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Shows the loading spinner overlay and traps focus within it
|
|
69
69
|
*/
|
|
70
70
|
function showLoadingSpinner() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Hides the loading spinner overlay and restores focus to the previously focused element
|
|
88
88
|
*/
|
|
89
89
|
function hideLoadingSpinner() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
_govcyOverlay.style.display = 'none';
|
|
91
|
+
_govcyOverlay.setAttribute('aria-hidden', 'true');
|
|
92
|
+
document.documentElement.style.overflow = '';
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
101
|
}
|
|
102
102
|
|
|
103
103
|
|
|
@@ -165,24 +165,40 @@ function _uploadFileEventHandler(event) {
|
|
|
165
165
|
formData.append('file', file); // Attach the actual file
|
|
166
166
|
formData.append('elementName', elementName); // Attach the field name for backend lookup
|
|
167
167
|
|
|
168
|
-
// 🚀
|
|
169
|
-
|
|
168
|
+
// 🚀 Build upload URL depending on mode (single / multiple/add / multiple/edit/:index)
|
|
169
|
+
var pathname = window.location.pathname;
|
|
170
|
+
var uploadUrl;
|
|
171
|
+
|
|
172
|
+
if (/\/multiple\/add\/?$/.test(pathname)) {
|
|
173
|
+
uploadUrl = `/apis/${siteId}/${pageUrl}/multiple/add/upload`;
|
|
174
|
+
} else {
|
|
175
|
+
var editMatch = pathname.match(/\/multiple\/edit\/(\d+)(?:\/|$)/);
|
|
176
|
+
if (editMatch) {
|
|
177
|
+
var idx = editMatch[1];
|
|
178
|
+
uploadUrl = `/apis/${siteId}/${pageUrl}/multiple/edit/${idx}/upload`;
|
|
179
|
+
} else {
|
|
180
|
+
uploadUrl = `/apis/${siteId}/${pageUrl}/upload`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fetch(uploadUrl, {
|
|
170
185
|
method: "POST",
|
|
171
186
|
headers: {
|
|
172
187
|
"X-CSRF-Token": csrfToken // 🔐 Pass CSRF token in custom header
|
|
173
188
|
},
|
|
174
189
|
body: formData
|
|
175
190
|
})
|
|
176
|
-
|
|
191
|
+
|
|
192
|
+
.then(function (response) {
|
|
177
193
|
// 🚀 CHANGED: fetch does not auto-throw on error codes → check manually
|
|
178
194
|
if (!response.ok) {
|
|
179
|
-
return response.json().then(function(errData) {
|
|
195
|
+
return response.json().then(function (errData) {
|
|
180
196
|
throw { response: { data: errData } };
|
|
181
197
|
});
|
|
182
198
|
}
|
|
183
199
|
return response.json();
|
|
184
200
|
})
|
|
185
|
-
.then(function(data) {
|
|
201
|
+
.then(function (data) {
|
|
186
202
|
// ✅ Success response
|
|
187
203
|
var sha256 = data.Data.sha256;
|
|
188
204
|
var fileId = data.Data.fileId;
|
|
@@ -190,7 +206,7 @@ function _uploadFileEventHandler(event) {
|
|
|
190
206
|
// 📝 Store returned metadata in hidden fields if needed
|
|
191
207
|
// document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
|
|
192
208
|
// document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
|
|
193
|
-
|
|
209
|
+
|
|
194
210
|
// Hide loading spinner
|
|
195
211
|
hideLoadingSpinner();
|
|
196
212
|
|
|
@@ -200,15 +216,15 @@ function _uploadFileEventHandler(event) {
|
|
|
200
216
|
// Accessibility: Update ARIA live region with success message
|
|
201
217
|
var statusRegion = document.getElementById('_govcy-upload-status');
|
|
202
218
|
if (statusRegion) {
|
|
203
|
-
setTimeout(function() {
|
|
219
|
+
setTimeout(function () {
|
|
204
220
|
statusRegion.textContent = messages.uploadSuccesful[lang];
|
|
205
221
|
}, 200);
|
|
206
|
-
setTimeout(function() {
|
|
222
|
+
setTimeout(function () {
|
|
207
223
|
statusRegion.textContent = '';
|
|
208
224
|
}, 5000);
|
|
209
225
|
}
|
|
210
226
|
})
|
|
211
|
-
.catch(function(err) {
|
|
227
|
+
.catch(function (err) {
|
|
212
228
|
// ⚠️ Show an error message if upload fails
|
|
213
229
|
var errorMessage = messages.uploadFailed;
|
|
214
230
|
var errorCode = err && err.response && err.response.data && err.response.data.ErrorCode;
|
|
@@ -245,7 +261,7 @@ function _uploadFileEventHandler(event) {
|
|
|
245
261
|
* @param {object} errorMessage The error message in all supported languages
|
|
246
262
|
*/
|
|
247
263
|
function _renderFileElement(elementState, elementId, elementName, fileId, sha256, errorMessage) {
|
|
248
|
-
|
|
264
|
+
|
|
249
265
|
// Grab the query string part (?foo=bar&route=something)
|
|
250
266
|
var queryString = window.location.search;
|
|
251
267
|
// Parse it
|
|
@@ -263,7 +279,7 @@ function _renderFileElement(elementState, elementId, elementName, fileId, sha256
|
|
|
263
279
|
"lang": lang
|
|
264
280
|
}
|
|
265
281
|
};
|
|
266
|
-
var fileInputMap =
|
|
282
|
+
var fileInputMap = JSON.parse(JSON.stringify(window._govcyFileInputs));
|
|
267
283
|
var fileElement = fileInputMap[elementName];
|
|
268
284
|
fileElement.element = elementState;
|
|
269
285
|
if (errorMessage != null) fileElement.params.error = errorMessage;
|
|
@@ -271,22 +287,27 @@ function _renderFileElement(elementState, elementId, elementName, fileId, sha256
|
|
|
271
287
|
if (sha256 != null) fileElement.params.sha256 = sha256;
|
|
272
288
|
if (elementState == "fileView") {
|
|
273
289
|
fileElement.params.visuallyHiddenText = fileElement.params.label;
|
|
274
|
-
//
|
|
275
|
-
|
|
290
|
+
// Use the actual current path (works for single, draft, edit)
|
|
291
|
+
var basePath = window.location.pathname.replace(/\/$/, "");
|
|
292
|
+
|
|
293
|
+
// View link
|
|
294
|
+
fileElement.params.viewHref = basePath + "/view-file/" + elementName;
|
|
276
295
|
fileElement.params.viewTarget = "_blank";
|
|
277
|
-
|
|
296
|
+
|
|
297
|
+
// Delete link (preserve ?route=review if present)
|
|
298
|
+
fileElement.params.deleteHref = basePath + "/delete-file/" + elementName
|
|
278
299
|
+ (route !== null ? "?route=" + encodeURIComponent(route) : "");
|
|
279
300
|
}
|
|
280
301
|
// Construct the JSONTemplate
|
|
281
302
|
var JSONTemplate = {
|
|
282
303
|
"elements": [fileElement]
|
|
283
304
|
};
|
|
284
|
-
|
|
305
|
+
|
|
285
306
|
//render HTML into string
|
|
286
|
-
var renderedHtml = renderer.renderFromJSON(JSONTemplate,inputData);
|
|
287
|
-
var outerElement = document.getElementById(`${elementId}-outer-control`)
|
|
288
|
-
|
|
289
|
-
|
|
307
|
+
var renderedHtml = renderer.renderFromJSON(JSONTemplate, inputData);
|
|
308
|
+
var outerElement = document.getElementById(`${elementId}-outer-control`)
|
|
309
|
+
|| document.getElementById(`${elementId}-input-control`)
|
|
310
|
+
|| document.getElementById(`${elementId}-view-control`);
|
|
290
311
|
|
|
291
312
|
if (outerElement) {
|
|
292
313
|
//remove all classes from outerElement
|