@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
|
@@ -26,6 +26,16 @@ export const staticResources = {
|
|
|
26
26
|
el: "Αλλαγή",
|
|
27
27
|
tr: "Değişiklik"
|
|
28
28
|
},
|
|
29
|
+
delete : {
|
|
30
|
+
en: "Delete",
|
|
31
|
+
el: "Διαγραφή",
|
|
32
|
+
tr: "Delete"
|
|
33
|
+
},
|
|
34
|
+
untitled : {
|
|
35
|
+
en: "Untitled",
|
|
36
|
+
el: "Χωρίς τίτλο",
|
|
37
|
+
tr: "Untitled"
|
|
38
|
+
},
|
|
29
39
|
formSuccess: {
|
|
30
40
|
en: "Your form has been submitted!",
|
|
31
41
|
el: "Η φόρμα σας έχει υποβληθεί!" ,
|
|
@@ -150,6 +160,66 @@ export const staticResources = {
|
|
|
150
160
|
en: "Υou have uploaded the same file more than once in this application. If you delete it, it will be deleted from all places in the application.",
|
|
151
161
|
el: "Έχετε ανεβάσει το αρχείο αυτό και σε άλλα σημεία της αίτησης. Αν το διαγράψετε, θα διαγραφεί από όλα τα σημεία.",
|
|
152
162
|
tr: "Υou have uploaded the same file more than once in this application. If you delete it, it will be deleted from all places in the application."
|
|
163
|
+
},
|
|
164
|
+
multipleThingsEnptyState: {
|
|
165
|
+
en: "You did not add any entries.",
|
|
166
|
+
el: "Δεν έχετε προσθέσει ακόμη κάποια καταχώρηση.",
|
|
167
|
+
tr: "You did not add any entries."
|
|
168
|
+
},
|
|
169
|
+
multipleThingsEmptyStateReview: {
|
|
170
|
+
en: "You did not add any entries.",
|
|
171
|
+
el: "Δεν έχετε προσθέσει κάποια καταχώρηση.",
|
|
172
|
+
tr: "You did not add any entries yet."
|
|
173
|
+
},
|
|
174
|
+
multipleThingsAddEntry: {
|
|
175
|
+
en: "Add new entry",
|
|
176
|
+
el: "Προσθήκη νέας καταχώρησης",
|
|
177
|
+
tr: "Add new entry"
|
|
178
|
+
},
|
|
179
|
+
multipleThingsDedupeMessage: {
|
|
180
|
+
en: "This entry already exists",
|
|
181
|
+
el: "Αυτή η καταχώριση υπάρχει ήδη",
|
|
182
|
+
tr: "This entry already exists"
|
|
183
|
+
},
|
|
184
|
+
multipleThingsMaxMessage: {
|
|
185
|
+
en: "You have reached the maximum number of entries. You can only add up to {{max}} entries",
|
|
186
|
+
el: "Έχετε φτάσει το μέγιστο αριθμό καταχωρίσεων. Μπορείτε να προσθέσετε μόνο έως {{max}} καταχωρίσεις",
|
|
187
|
+
tr: "You have reached the maximum number of entries. You can only add up to {{max}} entries"
|
|
188
|
+
},
|
|
189
|
+
multipleThingsMinMessage: {
|
|
190
|
+
en: "You have not added the minimum number of entries. You must add at least {{min}} entries",
|
|
191
|
+
el: "Δεν έχετε προσθέσει τον ελάχιστο αριθμό καταχωρίσεων. Πρέπει να προσθέσετε τουλάχιστον {{min}} καταχωρίσεις",
|
|
192
|
+
tr: "You have not added the minimum number of entries. You must add at least {{min}} entries"
|
|
193
|
+
},
|
|
194
|
+
multipleThingsItemsValidationPrefix: {
|
|
195
|
+
en: "Entry {{index}} - ",
|
|
196
|
+
el: "Καταχώρηση {{index}} - ",
|
|
197
|
+
tr: "Entry {{index}} - "
|
|
198
|
+
},
|
|
199
|
+
multipleThingsAddSuffix: {
|
|
200
|
+
en: " (Add)",
|
|
201
|
+
el: " (Προσθήκη)",
|
|
202
|
+
tr: " (Add)"
|
|
203
|
+
},
|
|
204
|
+
multipleThingsEditSuffix: {
|
|
205
|
+
en: " (Change)",
|
|
206
|
+
el: " (Αλλαγή)",
|
|
207
|
+
tr: " (Change)"
|
|
208
|
+
},
|
|
209
|
+
multipleThingsDeleteTitle: {
|
|
210
|
+
en: "Are you sure you want to delete the item \"{{item}}\"",
|
|
211
|
+
el: "Σίγουρα θέλετε να διαγράψετε την καταχώρηση \"{{item}}\"",
|
|
212
|
+
tr: "Are you sure you want to delete the item \"{{item}}\""
|
|
213
|
+
},
|
|
214
|
+
multipleThingsDeleteValidationError: {
|
|
215
|
+
en: "Select if you want to delete this item",
|
|
216
|
+
el: "Επιλέξτε αν θέλετε να διαγράψετε αυτή την καταχώρηση",
|
|
217
|
+
tr: "Select if you want to delete the item"
|
|
218
|
+
},
|
|
219
|
+
multipleThingsEntries: {
|
|
220
|
+
en: "Entries",
|
|
221
|
+
el: "Καταχωρήσεις",
|
|
222
|
+
tr: "Entries"
|
|
153
223
|
}
|
|
154
224
|
},
|
|
155
225
|
//remderer sections
|
|
@@ -475,6 +545,7 @@ export function getMultilingualObject(el, en, tr) {
|
|
|
475
545
|
*/
|
|
476
546
|
export function getSameMultilingualObject(languages, value) {
|
|
477
547
|
const obj = {};
|
|
548
|
+
if (!Array.isArray(languages)) return {el: value, en: value, tr: value};
|
|
478
549
|
for (const lang of languages) {
|
|
479
550
|
obj[lang.code] = value || "";
|
|
480
551
|
}
|
|
@@ -510,4 +581,61 @@ export function getEmailObject( subject, preHeader, header, username, body, foot
|
|
|
510
581
|
footerText: getLocalizeContent(footer, usedLang)
|
|
511
582
|
}
|
|
512
583
|
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Get the link for multiple things hub and add/edit/delete pages
|
|
588
|
+
* @param {string} linkType The type of link. Can be `add`, `edit`, `delete`
|
|
589
|
+
* @param {string} siteId The site id
|
|
590
|
+
* @param {string} pageUrl The page url
|
|
591
|
+
* @param {string} lang The page language
|
|
592
|
+
* @param {string} entryKey The entry key. If not provided, it will be set to an empty string.
|
|
593
|
+
* @param {string} route Whether it comes from the `review` route
|
|
594
|
+
* @param {string} linkText The link text. If not provided, it will be set to an empty string.
|
|
595
|
+
* @param {number} count The current count of entries. If not provided, it will be set to null.
|
|
596
|
+
* @returns {string} The link htmlElement govcy-frontend-renderer object
|
|
597
|
+
*/
|
|
598
|
+
export function getMultipleThingsLink(linkType, siteId, pageUrl, lang , entryKey = "", route = "", linkText = "", count = null) {
|
|
599
|
+
// Generate the action part of the URL based on the linkType
|
|
600
|
+
let actionPart = "";
|
|
601
|
+
let linkTextString = "";
|
|
602
|
+
switch (linkType) {
|
|
603
|
+
case "add":
|
|
604
|
+
actionPart = `multiple/add`;
|
|
605
|
+
// if linkText is not provided, use the default text from staticResources
|
|
606
|
+
linkTextString = (linkText
|
|
607
|
+
? linkText
|
|
608
|
+
: staticResources.text.multipleThingsAddEntry[lang] || staticResources.text.multipleThingsAddEntry["el"]
|
|
609
|
+
);
|
|
610
|
+
break;
|
|
611
|
+
case "edit":
|
|
612
|
+
actionPart = `multiple/edit/${entryKey}`;
|
|
613
|
+
linkTextString = staticResources.text.change[lang] || staticResources.text.change["el"];
|
|
614
|
+
break;
|
|
615
|
+
case "delete":
|
|
616
|
+
actionPart = `multiple/delete/${entryKey}`;
|
|
617
|
+
linkTextString = staticResources.text.delete[lang] || staticResources.text.delete["el"];
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
actionPart = `multiple/add`;
|
|
621
|
+
// if linkText is not provided, use the default text from staticResources
|
|
622
|
+
linkTextString = (linkText
|
|
623
|
+
? linkText
|
|
624
|
+
: staticResources.text.multipleThingsAddEntry[lang] || staticResources.text.multipleThingsAddEntry["el"]
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
// full part of the URL
|
|
628
|
+
const fullPath = `/${siteId}${pageUrl ? `/${pageUrl}` : ""}/${actionPart}/${route ? `?route=${route}` : ""}`;
|
|
629
|
+
// return the link htmlElement govcy-frontend-renderer object
|
|
630
|
+
return {
|
|
631
|
+
element: "htmlElement",
|
|
632
|
+
params: {
|
|
633
|
+
text: {
|
|
634
|
+
en: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
635
|
+
el: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
636
|
+
tr: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
513
641
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export function isApiRequest(req) {
|
|
11
11
|
const acceptJson = (req.headers?.accept || "").toLowerCase().includes("application/json");
|
|
12
12
|
|
|
13
|
-
const apiUrlPattern =
|
|
13
|
+
const apiUrlPattern = /^\/apis\/[^/]+\/[^/]+(?:\/.*)?\/(upload|download)$/;
|
|
14
14
|
const isStructuredApiUrl = apiUrlPattern.test(req.originalUrl || req.url);
|
|
15
15
|
|
|
16
16
|
return acceptJson || isStructuredApiUrl;
|
|
@@ -59,19 +59,33 @@ export function initializeSiteData(store, siteId, pageUrl = null) {
|
|
|
59
59
|
* @param {string} pageUrl The page url
|
|
60
60
|
* @param {object} validationErrors The validation errors
|
|
61
61
|
* @param {object} formData The form data that produced the errors
|
|
62
|
+
* @param {string} key The key to store the errors under. Used for multiple items. Defaults to null
|
|
62
63
|
*/
|
|
63
|
-
export function storePageValidationErrors(store, siteId, pageUrl, validationErrors, formData) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
export function storePageValidationErrors(store, siteId, pageUrl, validationErrors, formData, key = null) {
|
|
65
|
+
// Ensure session structure is initialized
|
|
66
|
+
initializeSiteData(store, siteId, pageUrl);
|
|
67
|
+
|
|
68
|
+
// Build the error object
|
|
69
|
+
const errorObj = {
|
|
70
|
+
errors: validationErrors,
|
|
71
|
+
formData: formData,
|
|
72
|
+
errorSummary: []
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// If a key is provided (e.g., "add" or "2"), store under that key
|
|
76
|
+
if (key !== null) {
|
|
77
|
+
const existing = store.siteData[siteId].inputData[pageUrl]["validationErrors"] || {};
|
|
68
78
|
store.siteData[siteId].inputData[pageUrl]["validationErrors"] = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
errorSummary: []
|
|
79
|
+
...existing,
|
|
80
|
+
[key]: errorObj
|
|
72
81
|
};
|
|
82
|
+
} else {
|
|
83
|
+
// Normal page (no key)
|
|
84
|
+
store.siteData[siteId].inputData[pageUrl]["validationErrors"] = errorObj;
|
|
85
|
+
}
|
|
73
86
|
}
|
|
74
87
|
|
|
88
|
+
|
|
75
89
|
/**
|
|
76
90
|
* Stores the page's form data in the data layer
|
|
77
91
|
*
|
|
@@ -378,10 +392,43 @@ export function getSiteSubmissionData(store, siteId) {
|
|
|
378
392
|
* @param {string} elementName The element name
|
|
379
393
|
* @returns The value of the form data for the element or an empty string if none exist.
|
|
380
394
|
*/
|
|
381
|
-
export function getFormDataValue(store, siteId, pageUrl, elementName) {
|
|
382
|
-
|
|
395
|
+
export function getFormDataValue(store, siteId, pageUrl, elementName, index = null) {
|
|
396
|
+
const pageData = store?.siteData?.[siteId]?.inputData?.[pageUrl];
|
|
397
|
+
if (!pageData) return "";
|
|
398
|
+
|
|
399
|
+
// Case 1: formData is an array (multipleThings edit)
|
|
400
|
+
if (Array.isArray(pageData.formData) && index !== null) {
|
|
401
|
+
return pageData?.formData[index]?.[elementName] || "";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Case 2: formData is a flat object (single page or multipleThings add seed)
|
|
405
|
+
if (pageData.formData && !Array.isArray(pageData.formData)) {
|
|
406
|
+
const val = pageData.formData[elementName];
|
|
407
|
+
// If the flat value exists and is non-empty, prefer it.
|
|
408
|
+
const hasNonEmptyFlat =
|
|
409
|
+
val !== undefined &&
|
|
410
|
+
val !== "" &&
|
|
411
|
+
!(typeof val === "object" && val !== null && Object.keys(val).length === 0);
|
|
412
|
+
if (hasNonEmptyFlat) return val;
|
|
413
|
+
|
|
414
|
+
// Otherwise, fall back to multipleDraft (used in add flow) if present.
|
|
415
|
+
if (pageData.multipleDraft && typeof pageData.multipleDraft === "object") {
|
|
416
|
+
const draftVal = pageData.multipleDraft[elementName];
|
|
417
|
+
if (draftVal !== undefined && draftVal !== "") return draftVal;
|
|
418
|
+
}
|
|
419
|
+
// If neither exists, return an empty string
|
|
420
|
+
return "";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Case 3: no flat formData; fall back to multipleDraft (used in add flow)
|
|
424
|
+
if (pageData.multipleDraft && typeof pageData.multipleDraft === "object") {
|
|
425
|
+
return pageData?.multipleDraft?.[elementName] || "";
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return "";
|
|
383
429
|
}
|
|
384
430
|
|
|
431
|
+
|
|
385
432
|
/**
|
|
386
433
|
* Get the user object from the session store
|
|
387
434
|
*
|
|
@@ -389,13 +436,51 @@ export function getFormDataValue(store, siteId, pageUrl, elementName) {
|
|
|
389
436
|
* @returns The user object from the store or null if it doesn't exist.
|
|
390
437
|
*/
|
|
391
438
|
export function getUser(store) {
|
|
392
|
-
return store
|
|
439
|
+
return store?.user || null;
|
|
393
440
|
}
|
|
394
441
|
|
|
395
442
|
export function clearSiteData(store, siteId) {
|
|
396
443
|
delete store?.siteData[siteId];
|
|
397
444
|
}
|
|
398
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Get multiple things draft data while adding a new multiple thing
|
|
448
|
+
*
|
|
449
|
+
* @param {object} store The session store
|
|
450
|
+
* @param {string} siteId The site id
|
|
451
|
+
* @param {string} pageUrl The page url
|
|
452
|
+
* @returns The multiple things draft data while adding a new multiple thing.
|
|
453
|
+
*/
|
|
454
|
+
export function getMultipleDraft(store, siteId, pageUrl) {
|
|
455
|
+
return store?.siteData?.[siteId]?.inputData?.[pageUrl]?.multipleDraft || null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Store multiple things draft data used while adding a new multiple thing
|
|
460
|
+
*
|
|
461
|
+
* @param {object} store The session store
|
|
462
|
+
* @param {string} siteId The site id
|
|
463
|
+
* @param {string} pageUrl The page url
|
|
464
|
+
* @param {*} obj The multiple things draft data to be stored
|
|
465
|
+
*/
|
|
466
|
+
export function setMultipleDraft(store, siteId, pageUrl, obj) {
|
|
467
|
+
initializeSiteData(store, siteId, pageUrl);
|
|
468
|
+
store.siteData[siteId].inputData[pageUrl].multipleDraft = obj || {};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Clear multiple things draft data used while adding a new multiple thing
|
|
473
|
+
*
|
|
474
|
+
* @param {object} store The session store
|
|
475
|
+
* @param {string} siteId The site id
|
|
476
|
+
* @param {string} pageUrl The page url
|
|
477
|
+
*/
|
|
478
|
+
export function clearMultipleDraft(store, siteId, pageUrl) {
|
|
479
|
+
if (store?.siteData?.[siteId]?.inputData?.[pageUrl]) {
|
|
480
|
+
store.siteData[siteId].inputData[pageUrl].multipleDraft = null;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
399
484
|
/**
|
|
400
485
|
* Check if a file reference is used in more than one place (field) across the site's inputData.
|
|
401
486
|
*
|
|
@@ -431,34 +516,55 @@ export function isFileUsedInSiteInputDataAgain(store, siteId, { fileId, sha256 }
|
|
|
431
516
|
|
|
432
517
|
// Loop all pages under the site
|
|
433
518
|
for (const pageKey of Object.keys(site)) {
|
|
434
|
-
const
|
|
435
|
-
if (!
|
|
436
|
-
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (idMatches && shaMatches) {
|
|
456
|
-
hits += 1;
|
|
457
|
-
// As soon as we see it in more than one place, we can answer true
|
|
458
|
-
if (hits > 1) return true;
|
|
519
|
+
const pageData = site[pageKey];
|
|
520
|
+
if (!pageData) continue;
|
|
521
|
+
|
|
522
|
+
// Helper to scan an object for file matches
|
|
523
|
+
const scanObject = (obj) => {
|
|
524
|
+
for (const value of Object.values(obj)) {
|
|
525
|
+
if (value == null) continue;
|
|
526
|
+
const candidates = Array.isArray(value) ? value : [value];
|
|
527
|
+
for (const candidate of candidates) {
|
|
528
|
+
if (
|
|
529
|
+
candidate &&
|
|
530
|
+
typeof candidate === "object" &&
|
|
531
|
+
"fileId" in candidate &&
|
|
532
|
+
"sha256" in candidate
|
|
533
|
+
) {
|
|
534
|
+
const idMatches = fileId ? candidate.fileId === fileId : true;
|
|
535
|
+
const shaMatches = sha256 ? candidate.sha256 === sha256 : true;
|
|
536
|
+
if (idMatches && shaMatches) {
|
|
537
|
+
hits += 1;
|
|
538
|
+
if (hits > 1) return true;
|
|
539
|
+
}
|
|
459
540
|
}
|
|
460
541
|
}
|
|
461
542
|
}
|
|
543
|
+
return false;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// Case 1: flat formData object
|
|
547
|
+
if (pageData.formData && !Array.isArray(pageData.formData)) {
|
|
548
|
+
if (scanObject(pageData.formData)) return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Case 2: multipleDraft
|
|
552
|
+
if (pageData.multipleDraft) {
|
|
553
|
+
if (scanObject(pageData.multipleDraft)) return true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Case 3: formData as array (multiple items)
|
|
557
|
+
if (Array.isArray(pageData.formData)) {
|
|
558
|
+
for (const item of pageData.formData) {
|
|
559
|
+
if (scanObject(item)) return true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Case 4: formData.multipleItems array (your current design)
|
|
564
|
+
if (Array.isArray(pageData.formData?.multipleItems)) {
|
|
565
|
+
for (const item of pageData.formData.multipleItems) {
|
|
566
|
+
if (scanObject(item)) return true;
|
|
567
|
+
}
|
|
462
568
|
}
|
|
463
569
|
}
|
|
464
570
|
|
|
@@ -498,6 +604,7 @@ export function removeAllFilesFromSite(
|
|
|
498
604
|
) {
|
|
499
605
|
// Ensure session structure is initialized
|
|
500
606
|
initializeSiteData(store, siteId);
|
|
607
|
+
|
|
501
608
|
// --- Guard rails ---------------------------------------------------------
|
|
502
609
|
|
|
503
610
|
// Nothing to remove if neither identifier is provided.
|
|
@@ -534,43 +641,77 @@ export function removeAllFilesFromSite(
|
|
|
534
641
|
// --- Main traversal over all pages --------------------------------------
|
|
535
642
|
|
|
536
643
|
for (const page of Object.values(site)) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
644
|
+
if (!page || typeof page !== "object") continue;
|
|
645
|
+
|
|
646
|
+
// --- Case 1: flat formData object -----------------------------------
|
|
647
|
+
if (page.formData && !Array.isArray(page.formData)) {
|
|
648
|
+
const formData = page.formData;
|
|
649
|
+
for (const key of Object.keys(formData)) {
|
|
650
|
+
const val = formData[key];
|
|
651
|
+
|
|
652
|
+
// Case A: a single file object → replace with "" if it matches.
|
|
653
|
+
if (isMatch(val)) {
|
|
654
|
+
formData[key] = "";
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Case B: an array → replace ONLY the matching items with "".
|
|
659
|
+
if (Array.isArray(val)) {
|
|
660
|
+
let changed = false;
|
|
661
|
+
const mapped = val.map((item) => {
|
|
662
|
+
if (isMatch(item)) {
|
|
663
|
+
changed = true;
|
|
664
|
+
return "";
|
|
665
|
+
}
|
|
666
|
+
return item;
|
|
667
|
+
});
|
|
668
|
+
if (changed) formData[key] = mapped;
|
|
669
|
+
}
|
|
556
670
|
}
|
|
671
|
+
}
|
|
557
672
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
673
|
+
// --- Case 2: formData as array (multiple items) ---------------------
|
|
674
|
+
if (Array.isArray(page.formData)) {
|
|
675
|
+
for (const item of page.formData) {
|
|
676
|
+
if (!item || typeof item !== "object") continue;
|
|
677
|
+
for (const key of Object.keys(item)) {
|
|
678
|
+
const val = item[key];
|
|
679
|
+
if (isMatch(val)) {
|
|
680
|
+
item[key] = "";
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (Array.isArray(val)) {
|
|
684
|
+
let changed = false;
|
|
685
|
+
const mapped = val.map((sub) =>
|
|
686
|
+
isMatch(sub) ? "" : sub
|
|
687
|
+
);
|
|
688
|
+
if (changed) item[key] = mapped;
|
|
565
689
|
}
|
|
566
|
-
|
|
567
|
-
});
|
|
568
|
-
if (changed) formData[key] = mapped;
|
|
690
|
+
}
|
|
569
691
|
}
|
|
692
|
+
}
|
|
570
693
|
|
|
571
|
-
|
|
572
|
-
|
|
694
|
+
// --- Case 3: multipleDraft ------------------------------------------
|
|
695
|
+
if (page.multipleDraft && typeof page.multipleDraft === "object") {
|
|
696
|
+
for (const key of Object.keys(page.multipleDraft)) {
|
|
697
|
+
const val = page.multipleDraft[key];
|
|
698
|
+
if (isMatch(val)) {
|
|
699
|
+
page.multipleDraft[key] = "";
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (Array.isArray(val)) {
|
|
703
|
+
let changed = false;
|
|
704
|
+
const mapped = val.map((sub) =>
|
|
705
|
+
isMatch(sub) ? "" : sub
|
|
706
|
+
);
|
|
707
|
+
if (changed) page.multipleDraft[key] = mapped;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
573
710
|
}
|
|
711
|
+
|
|
712
|
+
// Note: If you later store file-like objects deeper in nested objects,
|
|
713
|
+
// add a recursive visitor here (with cycle protection / max depth).
|
|
574
714
|
}
|
|
575
715
|
}
|
|
576
716
|
|
|
717
|
+
|