@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.4
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/CHANGELOG.md +60 -0
- package/README.md +4 -4
- package/dist/actions/body/converters.d.ts +20 -0
- package/dist/actions/body/converters.js +57 -0
- package/dist/actions/body/converters.js.map +1 -0
- package/dist/actions/body/escapeHtml.d.ts +6 -0
- package/dist/actions/body/escapeHtml.js +14 -0
- package/dist/actions/body/escapeHtml.js.map +1 -0
- package/dist/actions/body/serializeBody.d.ts +50 -0
- package/dist/actions/body/serializeBody.js +78 -0
- package/dist/actions/body/serializeBody.js.map +1 -0
- package/dist/actions/body/serializeSlate.js +31 -0
- package/dist/actions/body/serializeSlate.js.map +1 -0
- package/dist/actions/body/wildcards.d.ts +10 -0
- package/dist/actions/body/wildcards.js +33 -0
- package/dist/actions/body/wildcards.js.map +1 -0
- package/dist/actions/builtin/confirmation.js +40 -15
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +19 -12
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +13 -1
- package/dist/actions/builtin/index.js +18 -7
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/builtin/signedWebhook.js +6 -2
- package/dist/actions/builtin/signedWebhook.js.map +1 -1
- package/dist/actions/defineAction.d.ts +2 -1
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/dispatch.js +2 -1
- package/dist/actions/dispatch.js.map +1 -1
- package/dist/actions/runActions.js +10 -1
- package/dist/actions/runActions.js.map +1 -1
- package/dist/actions/task.js +7 -5
- package/dist/actions/task.js.map +1 -1
- package/dist/aggregation/resolveResultsRequest.d.ts +22 -6
- package/dist/aggregation/resolveResultsRequest.js +18 -7
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/normalizeCalc.js +5 -17
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/types.d.ts +8 -2
- package/dist/calc/types.js +22 -0
- package/dist/calc/types.js.map +1 -0
- package/dist/client/ByteSizeField.d.ts +21 -0
- package/dist/client/ByteSizeField.js +56 -0
- package/dist/client/ByteSizeField.js.map +1 -0
- package/dist/client/ConditionBuilder.js +2 -1
- package/dist/client/ConditionBuilder.js.map +1 -1
- package/dist/client/FieldNameSelect.d.ts +20 -0
- package/dist/client/FieldNameSelect.js +58 -0
- package/dist/client/FieldNameSelect.js.map +1 -0
- package/dist/client/FlowBuilder.d.ts +0 -1
- package/dist/client/FlowBuilder.js +102 -59
- package/dist/client/FlowBuilder.js.map +1 -1
- package/dist/client/FormConditionField.js +1 -4
- package/dist/client/FormConditionField.js.map +1 -1
- package/dist/client/admin.css +234 -0
- package/dist/client/flowAuthoring.js +35 -0
- package/dist/client/flowAuthoring.js.map +1 -0
- package/dist/client/synthesizeClientField.js +6 -2
- package/dist/client/synthesizeClientField.js.map +1 -1
- package/dist/client/toStaticLabel.js +13 -0
- package/dist/client/toStaticLabel.js.map +1 -0
- package/dist/collections/formSubmissions.js +49 -9
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +251 -88
- package/dist/collections/forms.js.map +1 -1
- package/dist/conditions/conditionType.js +9 -2
- package/dist/conditions/conditionType.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +2 -1
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/builtin/index.d.ts +11 -1
- package/dist/consent/builtin/index.js +15 -5
- package/dist/consent/builtin/index.js.map +1 -1
- package/dist/consent/builtin/static.js +7 -3
- package/dist/consent/builtin/static.js.map +1 -1
- package/dist/exports/client.d.ts +3 -1
- package/dist/exports/client.js +3 -1
- package/dist/exports/react.d.ts +15 -6
- package/dist/exports/react.js +10 -2
- package/dist/exports/rsc.d.ts +6 -1
- package/dist/exports/rsc.js +6 -1
- package/dist/exports/types.d.ts +4 -4
- package/dist/fields/buildFieldBlocks.js +20 -15
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.js +115 -2
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/consent.js +8 -4
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/date.js +29 -0
- package/dist/fields/builtin/date.js.map +1 -0
- package/dist/fields/builtin/file.d.ts +15 -0
- package/dist/fields/builtin/file.js +87 -7
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +24 -0
- package/dist/fields/builtin/index.js +29 -8
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +26 -0
- package/dist/fields/builtin/message.js.map +1 -0
- package/dist/fields/builtin/repeater.js +7 -3
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +7 -3
- package/dist/fields/builtin/select.js.map +1 -1
- package/dist/fields/fieldNamesOfType.js +25 -0
- package/dist/fields/fieldNamesOfType.js.map +1 -0
- package/dist/fields/localizedIf.d.ts +13 -0
- package/dist/fields/localizedIf.js +12 -0
- package/dist/fields/localizedIf.js.map +1 -0
- package/dist/fields/sharedConfig.js +82 -57
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +7 -2
- package/dist/flow/normalizeFlow.js +7 -1
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/flow/stepLabel.js +15 -0
- package/dist/flow/stepLabel.js.map +1 -0
- package/dist/form/pollState.d.ts +22 -0
- package/dist/form/pollState.js +17 -0
- package/dist/form/pollState.js.map +1 -0
- package/dist/form/toFormDocument.d.ts +60 -0
- package/dist/form/toFormDocument.js +39 -0
- package/dist/form/toFormDocument.js.map +1 -0
- package/dist/form/types.d.ts +48 -0
- package/dist/index.d.ts +81 -13
- package/dist/index.js +36 -15
- package/dist/index.js.map +1 -1
- package/dist/plugin/registerCollections.js +17 -37
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +11 -0
- package/dist/plugin/uploadsCollection.js +44 -0
- package/dist/plugin/uploadsCollection.js.map +1 -0
- package/dist/poll/applyPollOptions.js +17 -0
- package/dist/poll/applyPollOptions.js.map +1 -0
- package/dist/poll/buildPollOptionSourceFields.js +53 -0
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +35 -0
- package/dist/poll/definePollOptionSource.js +6 -0
- package/dist/poll/definePollOptionSource.js.map +1 -0
- package/dist/poll/registry.d.ts +16 -0
- package/dist/poll/registry.js +16 -0
- package/dist/poll/registry.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +28 -0
- package/dist/poll/resolvePollOptions.js +64 -0
- package/dist/poll/resolvePollOptions.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +21 -0
- package/dist/poll/resolvePollOutcome.js +36 -0
- package/dist/poll/resolvePollOutcome.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +1 -1
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/presentations/types.d.ts +3 -2
- package/dist/react/Form.d.ts +13 -31
- package/dist/react/Form.js +98 -85
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +35 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +55 -0
- package/dist/react/FormControls.js +56 -0
- package/dist/react/FormControls.js.map +1 -0
- package/dist/react/FormResults.d.ts +6 -3
- package/dist/react/FormResults.js +37 -29
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/FormSteps.d.ts +17 -0
- package/dist/react/FormSteps.js +35 -0
- package/dist/react/FormSteps.js.map +1 -0
- package/dist/react/Poll.d.ts +17 -6
- package/dist/react/Poll.js +66 -18
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -0
- package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
- package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/RecaptchaCaptcha.d.ts +41 -0
- package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
- package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/TurnstileCaptcha.d.ts +28 -0
- package/dist/react/captcha/TurnstileCaptcha.js +53 -0
- package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
- package/dist/react/captcha/types.d.ts +8 -0
- package/dist/react/captcha/useCaptchaScript.js +45 -0
- package/dist/react/captcha/useCaptchaScript.js.map +1 -0
- package/dist/react/captcha/vendors.js +8 -0
- package/dist/react/captcha/vendors.js.map +1 -0
- package/dist/react/fetchResults.d.ts +1 -1
- package/dist/react/fetchResults.js +1 -1
- package/dist/react/fetchResults.js.map +1 -1
- package/dist/react/primitives/Input.d.ts +1 -1
- package/dist/react/primitives/Input.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -1
- package/dist/react/renderers/checkbox.js +1 -1
- package/dist/react/renderers/consent.js +1 -1
- package/dist/react/renderers/date.js +37 -0
- package/dist/react/renderers/date.js.map +1 -0
- package/dist/react/renderers/email.js +1 -1
- package/dist/react/renderers/file.js +25 -8
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +4 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/message.js +35 -0
- package/dist/react/renderers/message.js.map +1 -0
- package/dist/react/renderers/number.js +1 -1
- package/dist/react/renderers/repeater.js +1 -1
- package/dist/react/renderers/select.js +1 -1
- package/dist/react/renderers/text.js +1 -1
- package/dist/react/renderers/textarea.js +1 -1
- package/dist/react/state.d.ts +25 -1
- package/dist/react/state.js +14 -1
- package/dist/react/state.js.map +1 -1
- package/dist/react/uploadFile.d.ts +2 -2
- package/dist/react/uploadFile.js +1 -1
- package/dist/react/uploadFile.js.map +1 -1
- package/dist/spam/captcha.d.ts +3 -2
- package/dist/spam/captcha.js +3 -2
- package/dist/spam/captcha.js.map +1 -1
- package/dist/spam/clientIp.js +9 -0
- package/dist/spam/clientIp.js.map +1 -0
- package/dist/spam/identify.js +3 -6
- package/dist/spam/identify.js.map +1 -1
- package/dist/spam/providers/hcaptcha.d.ts +17 -0
- package/dist/spam/providers/hcaptcha.js +18 -0
- package/dist/spam/providers/hcaptcha.js.map +1 -0
- package/dist/spam/providers/recaptcha.d.ts +23 -0
- package/dist/spam/providers/recaptcha.js +23 -0
- package/dist/spam/providers/recaptcha.js.map +1 -0
- package/dist/spam/providers/siteverify.js +34 -0
- package/dist/spam/providers/siteverify.js.map +1 -0
- package/dist/spam/providers/turnstile.d.ts +17 -0
- package/dist/spam/providers/turnstile.js +18 -0
- package/dist/spam/providers/turnstile.js.map +1 -0
- package/dist/spam/spamGuard.js +2 -5
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +2 -2
- package/dist/submissions/runSubmission.js +70 -33
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/validateSubmission.js +41 -7
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/votedCookie.d.ts +13 -0
- package/dist/submissions/votedCookie.js +27 -0
- package/dist/submissions/votedCookie.js.map +1 -0
- package/dist/translations/de.js +219 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +71 -10
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/index.d.ts +2 -1
- package/dist/translations/index.js +6 -2
- package/dist/translations/index.js.map +1 -1
- package/dist/translations/keys.d.ts +69 -8
- package/dist/translations/keys.js +70 -9
- package/dist/translations/keys.js.map +1 -1
- package/dist/uploads/formatBytes.d.ts +9 -0
- package/dist/uploads/formatBytes.js +25 -0
- package/dist/uploads/formatBytes.js.map +1 -0
- package/dist/uploads/resolveFileRef.js +1 -1
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/uploads/types.d.ts +1 -2
- package/dist/validation/builtin/index.d.ts +14 -0
- package/dist/validation/builtin/index.js +12 -1
- package/dist/validation/builtin/index.js.map +1 -1
- package/dist/validation/builtin/maxDate.js +23 -0
- package/dist/validation/builtin/maxDate.js.map +1 -0
- package/dist/validation/builtin/minDate.js +23 -0
- package/dist/validation/builtin/minDate.js.map +1 -0
- package/dist/validation/builtin/validateDateBound.js +18 -0
- package/dist/validation/builtin/validateDateBound.js.map +1 -0
- package/dist/validation/validateUrl.js +22 -0
- package/dist/validation/validateUrl.js.map +1 -0
- package/package.json +4 -2
- package/styles/form-builder.css +10 -190
- package/dist/collections/uploads.d.ts +0 -30
- package/dist/collections/uploads.js +0 -55
- package/dist/collections/uploads.js.map +0 -1
- package/dist/presentations/registry.d.ts +0 -15
- package/dist/presentations/registry.js +0 -15
- package/dist/presentations/registry.js.map +0 -1
- package/dist/react/toFormDocument.d.ts +0 -24
- package/dist/react/toFormDocument.js +0 -22
- package/dist/react/toFormDocument.js.map +0 -1
|
@@ -17,7 +17,6 @@ const mimeTypesOf = (raw) => {
|
|
|
17
17
|
return out.length > 0 ? out : void 0;
|
|
18
18
|
};
|
|
19
19
|
const fileFieldConfigOf = (instance) => ({
|
|
20
|
-
relationTo: typeof instance.relationTo === "string" ? instance.relationTo : void 0,
|
|
21
20
|
mimeTypes: mimeTypesOf(instance.mimeTypes),
|
|
22
21
|
maxSize: typeof instance.maxSize === "number" ? instance.maxSize : void 0
|
|
23
22
|
});
|
|
@@ -54,8 +53,10 @@ const optionLabelsFor = (instance) => {
|
|
|
54
53
|
* (conditions, validation, storage). Conditions gate the second pass against these effective answers: a
|
|
55
54
|
* field whose `visibleWhen` is false is skipped entirely (never validated, never stored, so a client-sent
|
|
56
55
|
* value for it is ignored), and a visible field whose `validateWhen` is false stores its value but skips
|
|
57
|
-
* validation. A visible calc field stores its derived value and is never validated.
|
|
58
|
-
*
|
|
56
|
+
* validation. A visible calc field stores its derived value and is never validated. Display-only field
|
|
57
|
+
* types (value kind 'none', e.g. message) are skipped in both passes: never validated, never stored, and
|
|
58
|
+
* a client-sent value under their name is dropped. Only `error` severity blocks; warnings are computed
|
|
59
|
+
* but not surfaced server-side (the renderer surfaces them).
|
|
59
60
|
*/
|
|
60
61
|
const runSubmission = async (input) => {
|
|
61
62
|
const { fields, values, registry, ruleRegistry, consentRegistry, locale, t, operation, req, payload, formId, uploadSlug, expectedOwner } = input;
|
|
@@ -65,7 +66,7 @@ const runSubmission = async (input) => {
|
|
|
65
66
|
for (const instance of fields) {
|
|
66
67
|
const definition = registry.get(instance.blockType);
|
|
67
68
|
const raw = incoming.get(instance.name);
|
|
68
|
-
if (!definition || calcExpressionOf(instance)) continue;
|
|
69
|
+
if (!definition || definition.value === "none" || calcExpressionOf(instance)) continue;
|
|
69
70
|
const effectiveRaw = instance.blockType === "consent" && isEmpty(raw) ? false : instance.blockType === "repeater" && isEmpty(raw) ? [] : raw;
|
|
70
71
|
if (isEmpty(effectiveRaw) && instance.blockType !== "repeater") continue;
|
|
71
72
|
const value = coerce(definition.value, effectiveRaw);
|
|
@@ -80,7 +81,7 @@ const runSubmission = async (input) => {
|
|
|
80
81
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
82
|
for (const instance of fields) {
|
|
82
83
|
const definition = registry.get(instance.blockType);
|
|
83
|
-
if (!definition) continue;
|
|
84
|
+
if (!definition || definition.value === "none") continue;
|
|
84
85
|
const raw = incoming.get(instance.name);
|
|
85
86
|
const value = coercedByName.has(instance.name) ? coercedByName.get(instance.name) : raw;
|
|
86
87
|
if (!evaluateCondition(instance.visibleWhen, effective)) continue;
|
|
@@ -122,15 +123,21 @@ const runSubmission = async (input) => {
|
|
|
122
123
|
continue;
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
|
-
if (
|
|
126
|
+
if (definition.value === "file") {
|
|
126
127
|
if (isEmpty(value)) continue;
|
|
127
128
|
if (payload) {
|
|
128
|
-
|
|
129
|
+
if (!uploadSlug) {
|
|
130
|
+
errors.push({
|
|
131
|
+
path: instance.name,
|
|
132
|
+
message: t(errorKeyFor("missing"))
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
129
136
|
const captured = await captureFileRef({
|
|
130
137
|
payload,
|
|
131
|
-
collectionSlug:
|
|
138
|
+
collectionSlug: uploadSlug,
|
|
132
139
|
uploadId: value,
|
|
133
|
-
config:
|
|
140
|
+
config: fileFieldConfigOf(instance),
|
|
134
141
|
req,
|
|
135
142
|
expectedOwner
|
|
136
143
|
});
|
|
@@ -187,29 +194,57 @@ const runSubmission = async (input) => {
|
|
|
187
194
|
for (const subField of subFields) {
|
|
188
195
|
const subDef = registry.get(subField.blockType);
|
|
189
196
|
if (!subDef) continue;
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
197
|
+
if (subDef.value === "none") continue;
|
|
198
|
+
const subPath = `${instance.name}[${rowIndex}].${subField.name}`;
|
|
199
|
+
if (evaluateCondition(subField.visibleWhen, row) && evaluateCondition(subField.validateWhen, row)) {
|
|
200
|
+
const { errors: subErrors } = await runValidation({
|
|
201
|
+
field: subField,
|
|
202
|
+
fieldDefinition: subDef,
|
|
203
|
+
value: row[subField.name],
|
|
204
|
+
fieldType: subField.blockType,
|
|
205
|
+
ruleRegistry,
|
|
206
|
+
answers: row,
|
|
207
|
+
locale,
|
|
208
|
+
t,
|
|
209
|
+
operation,
|
|
210
|
+
event: "submit",
|
|
211
|
+
mode: "server",
|
|
212
|
+
req,
|
|
213
|
+
payload,
|
|
214
|
+
formId
|
|
215
|
+
});
|
|
216
|
+
for (const issue of subErrors.filter((e) => e.severity === "error")) errors.push({
|
|
217
|
+
path: subPath,
|
|
218
|
+
message: issue.message
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (subDef.value === "file" && payload) {
|
|
222
|
+
const subValue = row[subField.name];
|
|
223
|
+
if (isEmpty(subValue)) continue;
|
|
224
|
+
if (!uploadSlug) {
|
|
225
|
+
errors.push({
|
|
226
|
+
path: subPath,
|
|
227
|
+
message: t(errorKeyFor("missing"))
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const captured = await captureFileRef({
|
|
232
|
+
payload,
|
|
233
|
+
collectionSlug: uploadSlug,
|
|
234
|
+
uploadId: subValue,
|
|
235
|
+
config: fileFieldConfigOf(subField),
|
|
236
|
+
req,
|
|
237
|
+
expectedOwner
|
|
238
|
+
});
|
|
239
|
+
if (!captured.ok) {
|
|
240
|
+
errors.push({
|
|
241
|
+
path: subPath,
|
|
242
|
+
message: t(errorKeyFor(captured.code))
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
row[subField.name] = captured.ref;
|
|
247
|
+
}
|
|
213
248
|
}
|
|
214
249
|
}
|
|
215
250
|
if (errors.length > 0) continue;
|
|
@@ -219,9 +254,11 @@ const runSubmission = async (input) => {
|
|
|
219
254
|
fieldType: sf.blockType,
|
|
220
255
|
...sf.options ? { optionLabels: optionLabelsFor(sf) ?? void 0 } : {}
|
|
221
256
|
}));
|
|
257
|
+
const displayOnly = new Set(subFields.filter((sf) => registry.get(sf.blockType)?.value === "none").map((sf) => sf.name));
|
|
258
|
+
const storedRows = displayOnly.size === 0 ? rows : rows.map((row) => Object.fromEntries(Object.entries(row).filter(([key]) => !displayOnly.has(key))));
|
|
222
259
|
outValues.push({
|
|
223
260
|
field: instance.name,
|
|
224
|
-
value:
|
|
261
|
+
value: storedRows
|
|
225
262
|
});
|
|
226
263
|
descriptors.push({
|
|
227
264
|
field: instance.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runSubmission.js","names":[],"sources":["../../src/submissions/runSubmission.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport type { ConsentProof } from '../consent/captureConsent'\nimport { captureConsent } from '../consent/captureConsent'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport { keys } from '../translations/keys'\nimport { captureFileRef } from '../uploads/captureFileRef'\nimport type { FileFieldConfig, FileRefError } from '../uploads/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\nimport type {\n\tFormFieldInstance,\n\tSubmissionDescriptor,\n\tSubmissionFieldError,\n\tSubmissionValue,\n} from './types'\n\nconst errorKeyFor = (code: FileRefError): string => {\n\tif (code === 'mimeType') {\n\t\treturn keys.validationFileMimeType\n\t}\n\tif (code === 'tooLarge') {\n\t\treturn keys.validationFileTooLarge\n\t}\n\treturn keys.validationFileMissing\n}\n\n/** Normalize the authored `mimeTypes` (a `hasMany` text field) to a `string[]`. */\nconst mimeTypesOf = (raw: unknown): string[] | undefined => {\n\tif (!Array.isArray(raw)) {\n\t\treturn undefined\n\t}\n\tconst out = (raw as unknown[]).filter((entry): entry is string => typeof entry === 'string')\n\treturn out.length > 0 ? out : undefined\n}\n\nconst fileFieldConfigOf = (instance: FormFieldInstance): FileFieldConfig => ({\n\trelationTo: typeof instance.relationTo === 'string' ? instance.relationTo : undefined,\n\tmimeTypes: mimeTypesOf(instance.mimeTypes),\n\tmaxSize: typeof instance.maxSize === 'number' ? instance.maxSize : undefined,\n})\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\nconst coerce = (kind: string, value: unknown): unknown => {\n\tif (value == null) {\n\t\treturn value\n\t}\n\tif (kind === 'number') {\n\t\tconst next = typeof value === 'number' ? value : Number(value)\n\t\treturn Number.isNaN(next) ? value : next\n\t}\n\tif (kind === 'boolean') {\n\t\t// A genuine boolean from the renderer passes through; a raw client string is parsed strictly so that\n\t\t// \"false\"/\"0\"/\"off\"/\"no\"/\"\" are not silently truthy (this matters for the required-consent check).\n\t\tif (typeof value === 'string') {\n\t\t\tconst normalized = value.trim().toLowerCase()\n\t\t\treturn !(\n\t\t\t\tnormalized === '' ||\n\t\t\t\tnormalized === 'false' ||\n\t\t\t\tnormalized === '0' ||\n\t\t\t\tnormalized === 'off' ||\n\t\t\t\tnormalized === 'no'\n\t\t\t)\n\t\t}\n\t\treturn Boolean(value)\n\t}\n\tif (kind === 'text') {\n\t\treturn typeof value === 'string' ? value : String(value)\n\t}\n\treturn value\n}\n\nconst optionLabelsFor = (instance: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = instance.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Array<{ label?: string; value?: string }>) {\n\t\tif (typeof option?.value === 'string') {\n\t\t\tmap[option.value] = option.label ?? option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\nexport type ConsentProofEntry = { field: string } & ConsentProof\n\nexport type RunSubmissionInput = {\n\tfields: FormFieldInstance[]\n\tvalues: SubmissionValue[]\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\tlocale: string\n\tt: Translate\n\toperation: 'create' | 'update'\n\treq?: PayloadRequest\n\tpayload?: Payload\n\tformId?: number | string\n\t/** Upload collection slug for file fields without an explicit `relationTo`. Defaults to `form-uploads`. */\n\tuploadSlug?: string\n\t/** Resolved request identity, verified against an upload's `owner` stamp when a file field is captured. */\n\texpectedOwner?: string\n}\n\nexport type RunSubmissionResult = {\n\terrors: SubmissionFieldError[]\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconsent: ConsentProofEntry[]\n}\n\n/**\n * Pure submission core, two-pass: first coerce every answered field to its typed kind (so cross-field\n * rules see coerced siblings), then validate each field through `runValidation` (required, intrinsic\n * facet, declarative rules), snapshotting a localized descriptor per answered field. Calc fields are\n * the trust boundary: their client-sent values are never seeded, their values are derived from their\n * expressions over the coerced answers, and those derived values are authoritative everywhere downstream\n * (conditions, validation, storage). Conditions gate the second pass against these effective answers: a\n * field whose `visibleWhen` is false is skipped entirely (never validated, never stored, so a client-sent\n * value for it is ignored), and a visible field whose `validateWhen` is false stores its value but skips\n * validation. A visible calc field stores its derived value and is never validated. Only `error` severity\n * blocks; warnings are computed but not surfaced server-side (the renderer surfaces them).\n */\nexport const runSubmission = async (input: RunSubmissionInput): Promise<RunSubmissionResult> => {\n\tconst {\n\t\tfields,\n\t\tvalues,\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentRegistry,\n\t\tlocale,\n\t\tt,\n\t\toperation,\n\t\treq,\n\t\tpayload,\n\t\tformId,\n\t\tuploadSlug,\n\t\texpectedOwner,\n\t} = input\n\tconst incoming = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst coercedAnswers: Record<string, unknown> = {}\n\tconst coercedByName = new Map<string, unknown>()\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tconst raw = incoming.get(instance.name)\n\t\t// Never seed a calc field's client value: its value is derived below, so the client cannot influence it (even for a self-referencing expression).\n\t\tif (!definition || calcExpressionOf(instance)) {\n\t\t\tcontinue\n\t\t}\n\t\t// A consent field's \"not agreed\" state is semantically meaningful: treat a missing value as\n\t\t// `false` so the intrinsic validate can enforce required-agreement (not optional = must be true).\n\t\t// A repeater with no rows is coerced to [] so validate() can check minRows. The empty-guard\n\t\t// below must not skip repeaters: isEmpty([]) is true, but [] still needs to reach validate()\n\t\t// so a minRows > 0 constraint correctly rejects a zero-row submission.\n\t\tconst effectiveRaw =\n\t\t\tinstance.blockType === 'consent' && isEmpty(raw)\n\t\t\t\t? false\n\t\t\t\t: instance.blockType === 'repeater' && isEmpty(raw)\n\t\t\t\t\t? []\n\t\t\t\t\t: raw\n\t\tif (isEmpty(effectiveRaw) && instance.blockType !== 'repeater') {\n\t\t\tcontinue\n\t\t}\n\t\tconst value = coerce(definition.value, effectiveRaw)\n\t\tcoercedAnswers[instance.name] = value\n\t\tcoercedByName.set(instance.name, value)\n\t}\n\n\t// Calc values are authoritative everywhere downstream (conditions, validation, storage), never the client-sent value.\n\tconst effective = computeCalcFields(fields, coercedAnswers)\n\n\tconst errors: SubmissionFieldError[] = []\n\tconst outValues: SubmissionValue[] = []\n\tconst descriptors: SubmissionDescriptor[] = []\n\tconst consentProofs: ConsentProofEntry[] = []\n\tconst now = new Date().toISOString()\n\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\tconst value = coercedByName.has(instance.name) ? coercedByName.get(instance.name) : raw\n\n\t\tif (!evaluateCondition(instance.visibleWhen, effective)) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// A calc field's value is server-derived and always valid: skip validation and the client value entirely, storing the computed result.\n\t\tif (calcExpressionOf(instance)) {\n\t\t\toutValues.push({ field: instance.name, value: effective[instance.name] })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (evaluateCondition(instance.validateWhen, effective)) {\n\t\t\tconst { errors: issues } = await runValidation({\n\t\t\t\tfield: instance,\n\t\t\t\tfieldDefinition: definition,\n\t\t\t\tvalue,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers: effective,\n\t\t\t\tlocale,\n\t\t\t\tt,\n\t\t\t\toperation,\n\t\t\t\tevent: 'submit',\n\t\t\t\tmode: 'server',\n\t\t\t\treq,\n\t\t\t\tpayload,\n\t\t\t\tformId,\n\t\t\t})\n\t\t\tconst blocking = issues.filter((issue) => issue.severity === 'error')\n\t\t\tif (blocking.length > 0) {\n\t\t\t\tfor (const issue of blocking) {\n\t\t\t\t\terrors.push({ path: instance.name, message: issue.message })\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif (instance.blockType === 'file') {\n\t\t\tif (isEmpty(value)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (payload) {\n\t\t\t\tconst fileConfig = fileFieldConfigOf(instance)\n\t\t\t\tconst slug = fileConfig.relationTo ?? uploadSlug ?? 'form-uploads'\n\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\tpayload,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t\tuploadId: value as string | number,\n\t\t\t\t\tconfig: fileConfig,\n\t\t\t\t\treq,\n\t\t\t\t\texpectedOwner,\n\t\t\t\t})\n\t\t\t\tif (!captured.ok) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutValues.push({ field: instance.name, value: captured.ref })\n\t\t\t\tdescriptors.push({\n\t\t\t\t\tfield: instance.name,\n\t\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutValues.push({ field: instance.name, value })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'consent' && payload) {\n\t\t\tconst proof = await captureConsent({\n\t\t\t\tfield: instance,\n\t\t\t\tagreed: value === true,\n\t\t\t\tregistry: consentRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\tlocale,\n\t\t\t\tnow,\n\t\t\t})\n\t\t\tconsentProofs.push({ field: instance.name, ...proof })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'repeater') {\n\t\t\tconst rows = Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []\n\t\t\tconst subFields = Array.isArray(instance.subFields)\n\t\t\t\t? (instance.subFields as FormFieldInstance[])\n\t\t\t\t: []\n\n\t\t\t// Per-row sub-field validation: evaluate each sub-field's visibleWhen against the\n\t\t\t// row's own values, then run its validation rules. Errors are reported with path\n\t\t\t// fieldName[rowIndex].subFieldName so the client can map them to the right input.\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tconst subDef = registry.get(subField.blockType)\n\t\t\t\t\tif (!subDef) continue\n\t\t\t\t\tif (!evaluateCondition(subField.visibleWhen, row)) continue\n\t\t\t\t\tif (!evaluateCondition(subField.validateWhen, row)) continue\n\t\t\t\t\tconst subValue = row[subField.name]\n\t\t\t\t\tconst { errors: subErrors } = await runValidation({\n\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\tfieldDefinition: subDef,\n\t\t\t\t\t\tvalue: subValue,\n\t\t\t\t\t\tfieldType: subField.blockType,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt,\n\t\t\t\t\t\toperation,\n\t\t\t\t\t\tevent: 'submit',\n\t\t\t\t\t\tmode: 'server',\n\t\t\t\t\t\treq,\n\t\t\t\t\t\tpayload,\n\t\t\t\t\t\tformId,\n\t\t\t\t\t})\n\t\t\t\t\tfor (const issue of subErrors.filter((e) => e.severity === 'error')) {\n\t\t\t\t\t\terrors.push({\n\t\t\t\t\t\t\tpath: `${instance.name}[${rowIndex}].${subField.name}`,\n\t\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst subFieldDescriptors: SubmissionDescriptor[] = subFields.map((sf) => ({\n\t\t\t\tfield: sf.name,\n\t\t\t\tlabel: sf.label ?? sf.name,\n\t\t\t\tfieldType: sf.blockType,\n\t\t\t\t...((sf.options as unknown) ? { optionLabels: optionLabelsFor(sf) ?? undefined } : {}),\n\t\t\t}))\n\n\t\t\toutValues.push({ field: instance.name, value: rows })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(subFieldDescriptors.length > 0 ? { subFieldDescriptors } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isEmpty(raw)) {\n\t\t\tcontinue\n\t\t}\n\n\t\toutValues.push({ field: instance.name, value })\n\t\tconst optionLabels = optionLabelsFor(instance)\n\t\tdescriptors.push({\n\t\t\tfield: instance.name,\n\t\t\tlabel: instance.label ?? instance.name,\n\t\t\tfieldType: instance.blockType,\n\t\t\t...(optionLabels ? { optionLabels } : {}),\n\t\t})\n\t}\n\n\treturn { errors, values: outValues, descriptors, consent: consentProofs }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runSubmission.js","names":[],"sources":["../../src/submissions/runSubmission.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport type { ConsentProof } from '../consent/captureConsent'\nimport { captureConsent } from '../consent/captureConsent'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport { keys } from '../translations/keys'\nimport { captureFileRef } from '../uploads/captureFileRef'\nimport type { FileFieldConfig, FileRefError } from '../uploads/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\nimport type {\n\tFormFieldInstance,\n\tSubmissionDescriptor,\n\tSubmissionFieldError,\n\tSubmissionValue,\n} from './types'\n\nconst errorKeyFor = (code: FileRefError): string => {\n\tif (code === 'mimeType') {\n\t\treturn keys.validationFileMimeType\n\t}\n\tif (code === 'tooLarge') {\n\t\treturn keys.validationFileTooLarge\n\t}\n\treturn keys.validationFileMissing\n}\n\n/** Normalize the authored `mimeTypes` (a `hasMany` text field) to a `string[]`. */\nconst mimeTypesOf = (raw: unknown): string[] | undefined => {\n\tif (!Array.isArray(raw)) {\n\t\treturn undefined\n\t}\n\tconst out = (raw as unknown[]).filter((entry): entry is string => typeof entry === 'string')\n\treturn out.length > 0 ? out : undefined\n}\n\nconst fileFieldConfigOf = (instance: FormFieldInstance): FileFieldConfig => ({\n\tmimeTypes: mimeTypesOf(instance.mimeTypes),\n\tmaxSize: typeof instance.maxSize === 'number' ? instance.maxSize : undefined,\n})\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\nconst coerce = (kind: string, value: unknown): unknown => {\n\tif (value == null) {\n\t\treturn value\n\t}\n\tif (kind === 'number') {\n\t\tconst next = typeof value === 'number' ? value : Number(value)\n\t\treturn Number.isNaN(next) ? value : next\n\t}\n\tif (kind === 'boolean') {\n\t\t// A genuine boolean from the renderer passes through; a raw client string is parsed strictly so that\n\t\t// \"false\"/\"0\"/\"off\"/\"no\"/\"\" are not silently truthy (this matters for the required-consent check).\n\t\tif (typeof value === 'string') {\n\t\t\tconst normalized = value.trim().toLowerCase()\n\t\t\treturn !(\n\t\t\t\tnormalized === '' ||\n\t\t\t\tnormalized === 'false' ||\n\t\t\t\tnormalized === '0' ||\n\t\t\t\tnormalized === 'off' ||\n\t\t\t\tnormalized === 'no'\n\t\t\t)\n\t\t}\n\t\treturn Boolean(value)\n\t}\n\tif (kind === 'text') {\n\t\treturn typeof value === 'string' ? value : String(value)\n\t}\n\treturn value\n}\n\nconst optionLabelsFor = (instance: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = instance.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Array<{ label?: string; value?: string }>) {\n\t\tif (typeof option?.value === 'string') {\n\t\t\tmap[option.value] = option.label ?? option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\nexport type ConsentProofEntry = { field: string } & ConsentProof\n\nexport type RunSubmissionInput = {\n\tfields: FormFieldInstance[]\n\tvalues: SubmissionValue[]\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\tlocale: string\n\tt: Translate\n\toperation: 'create' | 'update'\n\treq?: PayloadRequest\n\tpayload?: Payload\n\tformId?: number | string\n\t/**\n\t * The plugin-configured uploads collection slug. The block's stamped `collection` is for the\n\t * client only; the server resolves the slug from plugin config and fails closed without one.\n\t */\n\tuploadSlug?: string\n\t/** Resolved request identity, verified against an upload's `owner` stamp when a file field is captured. */\n\texpectedOwner?: string\n}\n\nexport type RunSubmissionResult = {\n\terrors: SubmissionFieldError[]\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconsent: ConsentProofEntry[]\n}\n\n/**\n * Pure submission core, two-pass: first coerce every answered field to its typed kind (so cross-field\n * rules see coerced siblings), then validate each field through `runValidation` (required, intrinsic\n * facet, declarative rules), snapshotting a localized descriptor per answered field. Calc fields are\n * the trust boundary: their client-sent values are never seeded, their values are derived from their\n * expressions over the coerced answers, and those derived values are authoritative everywhere downstream\n * (conditions, validation, storage). Conditions gate the second pass against these effective answers: a\n * field whose `visibleWhen` is false is skipped entirely (never validated, never stored, so a client-sent\n * value for it is ignored), and a visible field whose `validateWhen` is false stores its value but skips\n * validation. A visible calc field stores its derived value and is never validated. Display-only field\n * types (value kind 'none', e.g. message) are skipped in both passes: never validated, never stored, and\n * a client-sent value under their name is dropped. Only `error` severity blocks; warnings are computed\n * but not surfaced server-side (the renderer surfaces them).\n */\nexport const runSubmission = async (input: RunSubmissionInput): Promise<RunSubmissionResult> => {\n\tconst {\n\t\tfields,\n\t\tvalues,\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentRegistry,\n\t\tlocale,\n\t\tt,\n\t\toperation,\n\t\treq,\n\t\tpayload,\n\t\tformId,\n\t\tuploadSlug,\n\t\texpectedOwner,\n\t} = input\n\tconst incoming = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst coercedAnswers: Record<string, unknown> = {}\n\tconst coercedByName = new Map<string, unknown>()\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tconst raw = incoming.get(instance.name)\n\t\t// Never seed a calc field's client value: its value is derived below, so the client cannot influence it (even for a self-referencing expression).\n\t\t// A display-only ('none' kind) field carries no value at all, so a client-sent value under its name is dropped here.\n\t\tif (!definition || definition.value === 'none' || calcExpressionOf(instance)) {\n\t\t\tcontinue\n\t\t}\n\t\t// A consent field's \"not agreed\" state is semantically meaningful: treat a missing value as\n\t\t// `false` so the intrinsic validate can enforce required-agreement (not optional = must be true).\n\t\t// A repeater with no rows is coerced to [] so validate() can check minRows. The empty-guard\n\t\t// below must not skip repeaters: isEmpty([]) is true, but [] still needs to reach validate()\n\t\t// so a minRows > 0 constraint correctly rejects a zero-row submission.\n\t\tconst effectiveRaw =\n\t\t\tinstance.blockType === 'consent' && isEmpty(raw)\n\t\t\t\t? false\n\t\t\t\t: instance.blockType === 'repeater' && isEmpty(raw)\n\t\t\t\t\t? []\n\t\t\t\t\t: raw\n\t\tif (isEmpty(effectiveRaw) && instance.blockType !== 'repeater') {\n\t\t\tcontinue\n\t\t}\n\t\tconst value = coerce(definition.value, effectiveRaw)\n\t\tcoercedAnswers[instance.name] = value\n\t\tcoercedByName.set(instance.name, value)\n\t}\n\n\t// Calc values are authoritative everywhere downstream (conditions, validation, storage), never the client-sent value.\n\tconst effective = computeCalcFields(fields, coercedAnswers)\n\n\tconst errors: SubmissionFieldError[] = []\n\tconst outValues: SubmissionValue[] = []\n\tconst descriptors: SubmissionDescriptor[] = []\n\tconst consentProofs: ConsentProofEntry[] = []\n\tconst now = new Date().toISOString()\n\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\t// A 'none'-kind (display-only) field is never validated and never stored: no value, no descriptor.\n\t\tif (!definition || definition.value === 'none') {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\tconst value = coercedByName.has(instance.name) ? coercedByName.get(instance.name) : raw\n\n\t\tif (!evaluateCondition(instance.visibleWhen, effective)) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// A calc field's value is server-derived and always valid: skip validation and the client value entirely, storing the computed result.\n\t\tif (calcExpressionOf(instance)) {\n\t\t\toutValues.push({ field: instance.name, value: effective[instance.name] })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (evaluateCondition(instance.validateWhen, effective)) {\n\t\t\tconst { errors: issues } = await runValidation({\n\t\t\t\tfield: instance,\n\t\t\t\tfieldDefinition: definition,\n\t\t\t\tvalue,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers: effective,\n\t\t\t\tlocale,\n\t\t\t\tt,\n\t\t\t\toperation,\n\t\t\t\tevent: 'submit',\n\t\t\t\tmode: 'server',\n\t\t\t\treq,\n\t\t\t\tpayload,\n\t\t\t\tformId,\n\t\t\t})\n\t\t\tconst blocking = issues.filter((issue) => issue.severity === 'error')\n\t\t\tif (blocking.length > 0) {\n\t\t\t\tfor (const issue of blocking) {\n\t\t\t\t\terrors.push({ path: instance.name, message: issue.message })\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Gate on the field-type definition's value kind, not the blockType literal, so a custom\n\t\t// type registered with value:'file' gets the same server-side capture and enforcement as\n\t\t// the built-in file field (a blockType check would leave it storing the raw client id).\n\t\tif (definition.value === 'file') {\n\t\t\tif (isEmpty(value)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (payload) {\n\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor('missing')) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fileConfig = fileFieldConfigOf(instance)\n\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\tpayload,\n\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\tuploadId: value as string | number,\n\t\t\t\t\tconfig: fileConfig,\n\t\t\t\t\treq,\n\t\t\t\t\texpectedOwner,\n\t\t\t\t})\n\t\t\t\tif (!captured.ok) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutValues.push({ field: instance.name, value: captured.ref })\n\t\t\t\tdescriptors.push({\n\t\t\t\t\tfield: instance.name,\n\t\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutValues.push({ field: instance.name, value })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'consent' && payload) {\n\t\t\tconst proof = await captureConsent({\n\t\t\t\tfield: instance,\n\t\t\t\tagreed: value === true,\n\t\t\t\tregistry: consentRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\tlocale,\n\t\t\t\tnow,\n\t\t\t})\n\t\t\tconsentProofs.push({ field: instance.name, ...proof })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'repeater') {\n\t\t\tconst rows = Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []\n\t\t\tconst subFields = Array.isArray(instance.subFields)\n\t\t\t\t? (instance.subFields as FormFieldInstance[])\n\t\t\t\t: []\n\n\t\t\t// Per-row sub-field processing. Validation is gated by the sub-field's visibleWhen and\n\t\t\t// validateWhen against the row's own values; errors carry the path\n\t\t\t// fieldName[rowIndex].subFieldName so the client maps them to the right input. File\n\t\t\t// sub-fields additionally cross the server trust boundary: they are captured\n\t\t\t// unconditionally (a crafted request could carry an id for a conditionally-hidden field),\n\t\t\t// so a stored row never holds a raw, unenforced upload id.\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tconst subDef = registry.get(subField.blockType)\n\t\t\t\t\tif (!subDef) continue\n\t\t\t\t\t// Display-only ('none' kind) sub-fields mirror the top-level skip: never validated,\n\t\t\t\t\t// never captured, and stripped from the stored row below so a client-injected value\n\t\t\t\t\t// under their name cannot ride along.\n\t\t\t\t\tif (subDef.value === 'none') continue\n\t\t\t\t\tconst subPath = `${instance.name}[${rowIndex}].${subField.name}`\n\t\t\t\t\tif (\n\t\t\t\t\t\tevaluateCondition(subField.visibleWhen, row) &&\n\t\t\t\t\t\tevaluateCondition(subField.validateWhen, row)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst { errors: subErrors } = await runValidation({\n\t\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\t\tfieldDefinition: subDef,\n\t\t\t\t\t\t\tvalue: row[subField.name],\n\t\t\t\t\t\t\tfieldType: subField.blockType,\n\t\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\tt,\n\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\tevent: 'submit',\n\t\t\t\t\t\t\tmode: 'server',\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tformId,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tfor (const issue of subErrors.filter((e) => e.severity === 'error')) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: issue.message })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Mirror the top-level file capture: the client submits only an upload id, so\n\t\t\t\t\t// re-derive filename/mimeType/filesize from the stored doc and enforce\n\t\t\t\t\t// owner/mime/size against the sub-field's config, failing closed on a missing\n\t\t\t\t\t// uploads slug or any capture rejection. The captured FileRef replaces the raw\n\t\t\t\t\t// id in the row. Without a payload (pure unit context) the value stays verbatim,\n\t\t\t\t\t// exactly like the top-level path.\n\t\t\t\t\tif (subDef.value === 'file' && payload) {\n\t\t\t\t\t\tconst subValue = row[subField.name]\n\t\t\t\t\t\tif (isEmpty(subValue)) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor('missing')) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\t\t\tuploadId: subValue as string | number,\n\t\t\t\t\t\t\tconfig: fileFieldConfigOf(subField),\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\texpectedOwner,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (!captured.ok) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\trow[subField.name] = captured.ref\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst subFieldDescriptors: SubmissionDescriptor[] = subFields.map((sf) => ({\n\t\t\t\tfield: sf.name,\n\t\t\t\tlabel: sf.label ?? sf.name,\n\t\t\t\tfieldType: sf.blockType,\n\t\t\t\t...((sf.options as unknown) ? { optionLabels: optionLabelsFor(sf) ?? undefined } : {}),\n\t\t\t}))\n\n\t\t\t// Strip display-only sub-fields from the stored rows: like top-level 'none' fields they\n\t\t\t// carry no answer, so a client-injected value under their name must never persist.\n\t\t\tconst displayOnly = new Set(\n\t\t\t\tsubFields.filter((sf) => registry.get(sf.blockType)?.value === 'none').map((sf) => sf.name)\n\t\t\t)\n\t\t\tconst storedRows =\n\t\t\t\tdisplayOnly.size === 0\n\t\t\t\t\t? rows\n\t\t\t\t\t: rows.map((row) =>\n\t\t\t\t\t\t\tObject.fromEntries(Object.entries(row).filter(([key]) => !displayOnly.has(key)))\n\t\t\t\t\t\t)\n\n\t\t\toutValues.push({ field: instance.name, value: storedRows })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(subFieldDescriptors.length > 0 ? { subFieldDescriptors } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isEmpty(raw)) {\n\t\t\tcontinue\n\t\t}\n\n\t\toutValues.push({ field: instance.name, value })\n\t\tconst optionLabels = optionLabelsFor(instance)\n\t\tdescriptors.push({\n\t\t\tfield: instance.name,\n\t\t\tlabel: instance.label ?? instance.name,\n\t\t\tfieldType: instance.blockType,\n\t\t\t...(optionLabels ? { optionLabels } : {}),\n\t\t})\n\t}\n\n\treturn { errors, values: outValues, descriptors, consent: consentProofs }\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { asFieldTranslate, asTranslate } from "../translations/server.js";
|
|
3
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
2
4
|
import { IDENTITY_CONTEXT_KEY } from "../spam/constants.js";
|
|
3
|
-
import "../collections/forms.js";
|
|
5
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
6
|
+
import { applyPollOptions } from "../poll/applyPollOptions.js";
|
|
7
|
+
import { resolvePollOptions } from "../poll/resolvePollOptions.js";
|
|
4
8
|
import { runSubmission } from "./runSubmission.js";
|
|
9
|
+
import { POLL_CONTEXT_KEY } from "./votedCookie.js";
|
|
5
10
|
import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
|
|
6
|
-
import { ValidationError } from "payload";
|
|
11
|
+
import { APIError, ValidationError } from "payload";
|
|
7
12
|
//#region src/submissions/validateSubmission.ts
|
|
8
13
|
/**
|
|
9
14
|
* Server-authoritative submission validation. On create it loads the referenced form, re-runs every
|
|
@@ -12,20 +17,49 @@ import { ValidationError } from "payload";
|
|
|
12
17
|
* per-field paths on any error-severity failure. The client is never trusted.
|
|
13
18
|
* Consent fields are captured into `result.consent` (array of proofs, one per visible consent field).
|
|
14
19
|
*/
|
|
15
|
-
const validateSubmission = ({ registry, ruleRegistry, consentRegistry, uploadSlug }) => async ({ data, operation, req }) => {
|
|
20
|
+
const validateSubmission = ({ registry, ruleRegistry, consentRegistry, uploadSlug, pollSourceRegistry }) => async ({ data, operation, req }) => {
|
|
16
21
|
if (operation !== "create" || !data) return data;
|
|
17
22
|
const formId = data.form;
|
|
18
23
|
if (formId == null) return data;
|
|
19
|
-
const
|
|
20
|
-
collection:
|
|
24
|
+
const form = await req.payload.findByID({
|
|
25
|
+
collection: FORMS_SLUG,
|
|
21
26
|
id: formId,
|
|
22
27
|
depth: 0,
|
|
23
28
|
locale: req.locale,
|
|
24
29
|
req
|
|
25
|
-
})
|
|
30
|
+
});
|
|
31
|
+
const poll = pollConfigOf(form.poll);
|
|
32
|
+
req.context[POLL_CONTEXT_KEY] = poll ?? null;
|
|
33
|
+
if (poll?.enabled === true && isPollClosed(poll)) throw new APIError(asTranslate(req.i18n.t)(keys.pollClosed), 403);
|
|
34
|
+
let fields = form.fields ?? [];
|
|
26
35
|
const incoming = data.values ?? [];
|
|
27
36
|
const locale = req.locale ?? "en";
|
|
28
37
|
const t = asFieldTranslate(req.i18n.t);
|
|
38
|
+
if (poll?.enabled === true && typeof poll.optionSource === "string" && poll.optionSource) {
|
|
39
|
+
let resolved;
|
|
40
|
+
try {
|
|
41
|
+
resolved = await resolvePollOptions({
|
|
42
|
+
payload: req.payload,
|
|
43
|
+
req,
|
|
44
|
+
form,
|
|
45
|
+
sources: pollSourceRegistry ?? /* @__PURE__ */ new Map()
|
|
46
|
+
}) ?? [];
|
|
47
|
+
} catch {
|
|
48
|
+
throw new APIError(asTranslate(req.i18n.t)(keys.pollOptionsUnavailable), 503);
|
|
49
|
+
}
|
|
50
|
+
const resultsField = typeof poll.resultsField === "string" ? poll.resultsField : void 0;
|
|
51
|
+
fields = applyPollOptions(fields, resultsField, resolved);
|
|
52
|
+
if (resultsField) {
|
|
53
|
+
const answer = incoming.find((entry) => entry.field === resultsField)?.value;
|
|
54
|
+
if (answer != null && answer !== "" && !resolved.some((option) => option.value === answer)) throw new ValidationError({
|
|
55
|
+
collection: FORM_SUBMISSIONS_SLUG,
|
|
56
|
+
errors: [{
|
|
57
|
+
path: resultsField,
|
|
58
|
+
message: asTranslate(req.i18n.t)(keys.validationSelect)
|
|
59
|
+
}]
|
|
60
|
+
}, req.t);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
29
63
|
const expectedOwner = typeof req.context?.["formBuilderSpamIdentity"] === "string" ? req.context[IDENTITY_CONTEXT_KEY] : void 0;
|
|
30
64
|
const result = await runSubmission({
|
|
31
65
|
fields,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateSubmission.js","names":[],"sources":["../../src/submissions/validateSubmission.ts"],"sourcesContent":["import { type CollectionBeforeValidateHook, ValidationError } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { IDENTITY_CONTEXT_KEY } from '../spam/constants'\nimport { asFieldTranslate } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runSubmission } from './runSubmission'\nimport type { FormFieldInstance, SubmissionValue } from './types'\n\nexport type ValidateSubmissionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\t/**
|
|
1
|
+
{"version":3,"file":"validateSubmission.js","names":[],"sources":["../../src/submissions/validateSubmission.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook, ValidationError } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { isPollClosed, pollConfigOf } from '../form/pollState'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { resolvePollOptions } from '../poll/resolvePollOptions'\nimport { IDENTITY_CONTEXT_KEY } from '../spam/constants'\nimport { keys } from '../translations/keys'\nimport { asFieldTranslate, asTranslate } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runSubmission } from './runSubmission'\nimport type { FormFieldInstance, SubmissionValue } from './types'\nimport { POLL_CONTEXT_KEY } from './votedCookie'\n\nexport type ValidateSubmissionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Registered poll option sources; a form's configured `optionSource` resolves through this at validation time. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n}\n\n/**\n * Server-authoritative submission validation. On create it loads the referenced form, re-runs every\n * field's required check, intrinsic validator, and declarative rules through `runSubmission`, threading\n * `req`/`payload` so server-only async rules can hit the DB, and throws a Payload `ValidationError` with\n * per-field paths on any error-severity failure. The client is never trusted.\n * Consent fields are captured into `result.consent` (array of proofs, one per visible consent field).\n */\nexport const validateSubmission =\n\t({\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentRegistry,\n\t\tuploadSlug,\n\t\tpollSourceRegistry,\n\t}: ValidateSubmissionArgs): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst formId = data.form\n\t\tif (formId == null) {\n\t\t\treturn data\n\t\t}\n\n\t\tconst form = await req.payload.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: formId as string | number,\n\t\t\tdepth: 0,\n\t\t\tlocale: req.locale,\n\t\t\treq,\n\t\t})\n\n\t\t// Stash the poll config (null = form has none) so the voted-cookie afterChange hook can\n\t\t// skip a second form fetch on the same request.\n\t\tconst poll = pollConfigOf(form.poll)\n\t\treq.context[POLL_CONTEXT_KEY] = poll ?? null\n\n\t\t// Form-level lifecycle guard, before any field work: a closed poll accepts no submissions,\n\t\t// regardless of what the client rendered.\n\t\tif (poll?.enabled === true && isPollClosed(poll)) {\n\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollClosed), 403)\n\t\t}\n\n\t\tlet fields = ((form.fields as FormFieldInstance[] | undefined) ?? []) as FormFieldInstance[]\n\t\tconst incoming = ((data.values as SubmissionValue[] | undefined) ?? []) as SubmissionValue[]\n\t\tconst locale = req.locale ?? 'en'\n\t\tconst t = asFieldTranslate(req.i18n.t)\n\n\t\t// With an option source configured, the source's resolved values are the only accepted\n\t\t// answers for the results field: options are injected into the field instance (so the\n\t\t// select's membership check and the stored option labels use them) and membership is also\n\t\t// enforced directly, so an empty resolution or a non-select results field still fails\n\t\t// closed. A resolve failure rejects the whole submission rather than skipping the check.\n\t\tif (poll?.enabled === true && typeof poll.optionSource === 'string' && poll.optionSource) {\n\t\t\tlet resolved: PollOption[]\n\t\t\ttry {\n\t\t\t\tresolved =\n\t\t\t\t\t(await resolvePollOptions({\n\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\treq,\n\t\t\t\t\t\tform,\n\t\t\t\t\t\tsources: pollSourceRegistry ?? new Map(),\n\t\t\t\t\t})) ?? []\n\t\t\t} catch {\n\t\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollOptionsUnavailable), 503)\n\t\t\t}\n\t\t\tconst resultsField = typeof poll.resultsField === 'string' ? poll.resultsField : undefined\n\t\t\tfields = applyPollOptions(fields, resultsField, resolved)\n\t\t\tif (resultsField) {\n\t\t\t\tconst answer = incoming.find((entry) => entry.field === resultsField)?.value\n\t\t\t\tconst isAnswered = answer != null && answer !== ''\n\t\t\t\tif (isAnswered && !resolved.some((option) => option.value === answer)) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t\t{ path: resultsField, message: asTranslate(req.i18n.t)(keys.validationSelect) },\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t\treq.t\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst expectedOwner =\n\t\t\ttypeof req.context?.[IDENTITY_CONTEXT_KEY] === 'string'\n\t\t\t\t? (req.context[IDENTITY_CONTEXT_KEY] as string)\n\t\t\t\t: undefined\n\n\t\tconst result = await runSubmission({\n\t\t\tfields,\n\t\t\tvalues: incoming,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tlocale,\n\t\t\tt,\n\t\t\toperation: 'create',\n\t\t\treq,\n\t\t\tpayload: req.payload,\n\t\t\tformId: formId as number | string,\n\t\t\tuploadSlug,\n\t\t\texpectedOwner,\n\t\t})\n\n\t\tif (result.errors.length > 0) {\n\t\t\tthrow new ValidationError({ collection: FORM_SUBMISSIONS_SLUG, errors: result.errors }, req.t)\n\t\t}\n\n\t\tdata.values = result.values\n\t\tdata.descriptors = result.descriptors\n\t\tdata.consent = result.consent.length > 0 ? result.consent : undefined\n\t\tdata.locale = locale\n\t\t// Unauthenticated submits are always 'complete'. A client-supplied 'partial' would\n\t\t// silently skip all post-submit actions and events. Authenticated callers (e.g. an\n\t\t// admin draft-save flow) may set status themselves.\n\t\tif (!req.user) {\n\t\t\tdata.status = 'complete'\n\t\t}\n\t\treturn data\n\t}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmCA,MAAa,sBACX,EACA,UACA,cACA,iBACA,YACA,yBAED,OAAO,EAAE,MAAM,WAAW,UAAU;CACnC,IAAI,cAAc,YAAY,CAAC,MAC9B,OAAO;CAER,MAAM,SAAS,KAAK;CACpB,IAAI,UAAU,MACb,OAAO;CAGR,MAAM,OAAO,MAAM,IAAI,QAAQ,SAAS;EACvC,YAAY;EACZ,IAAI;EACJ,OAAO;EACP,QAAQ,IAAI;EACZ;CACD,CAAC;CAID,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,QAAQ,oBAAoB,QAAQ;CAIxC,IAAI,MAAM,YAAY,QAAQ,aAAa,IAAI,GAC9C,MAAM,IAAI,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,UAAU,GAAG,GAAG;CAGjE,IAAI,SAAW,KAAK,UAA8C,CAAC;CACnE,MAAM,WAAa,KAAK,UAA4C,CAAC;CACrE,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,IAAI,iBAAiB,IAAI,KAAK,CAAC;CAOrC,IAAI,MAAM,YAAY,QAAQ,OAAO,KAAK,iBAAiB,YAAY,KAAK,cAAc;EACzF,IAAI;EACJ,IAAI;GACH,WACE,MAAM,mBAAmB;IACzB,SAAS,IAAI;IACb;IACA;IACA,SAAS,sCAAsB,IAAI,IAAI;GACxC,CAAC,KAAM,CAAC;EACV,QAAQ;GACP,MAAM,IAAI,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,sBAAsB,GAAG,GAAG;EAC7E;EACA,MAAM,eAAe,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe,KAAA;EACjF,SAAS,iBAAiB,QAAQ,cAAc,QAAQ;EACxD,IAAI,cAAc;GACjB,MAAM,SAAS,SAAS,MAAM,UAAU,MAAM,UAAU,YAAY,GAAG;GAEvE,IADmB,UAAU,QAAQ,WAAW,MAC9B,CAAC,SAAS,MAAM,WAAW,OAAO,UAAU,MAAM,GACnE,MAAM,IAAI,gBACT;IACC,YAAY;IACZ,QAAQ,CACP;KAAE,MAAM;KAAc,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,gBAAgB;IAAE,CAC/E;GACD,GACA,IAAI,CACL;EAEF;CACD;CACA,MAAM,gBACL,OAAO,IAAI,UAAA,+BAAoC,WAC3C,IAAI,QAAQ,wBACb,KAAA;CAEJ,MAAM,SAAS,MAAM,cAAc;EAClC;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,SAAS,IAAI;EACL;EACR;EACA;CACD,CAAC;CAED,IAAI,OAAO,OAAO,SAAS,GAC1B,MAAM,IAAI,gBAAgB;EAAE,YAAY;EAAuB,QAAQ,OAAO;CAAO,GAAG,IAAI,CAAC;CAG9F,KAAK,SAAS,OAAO;CACrB,KAAK,cAAc,OAAO;CAC1B,KAAK,UAAU,OAAO,QAAQ,SAAS,IAAI,OAAO,UAAU,KAAA;CAC5D,KAAK,SAAS;CAId,IAAI,CAAC,IAAI,MACR,KAAK,SAAS;CAEf,OAAO;AACR"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/submissions/votedCookie.d.ts
|
|
2
|
+
/** Name of the httpOnly voted cookie for a form: `fb-voted-{formId}`. */
|
|
3
|
+
declare const votedCookieName: (formId: number | string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a request's `Cookie` header carries the voted marker for a form. For SSR hosts: read
|
|
6
|
+
* the header (e.g. Next's `(await headers()).get('cookie')`) and pass the result to `<Poll hasVoted>`.
|
|
7
|
+
* The cookie is httpOnly (set server-side when the plugin's `poll.votedCookie` option is on), so
|
|
8
|
+
* this server read is the only way to consume it.
|
|
9
|
+
*/
|
|
10
|
+
declare const hasVotedCookie: (cookieHeader: string | null | undefined, formId: number | string) => boolean;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { hasVotedCookie, votedCookieName };
|
|
13
|
+
//# sourceMappingURL=votedCookie.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/submissions/votedCookie.ts
|
|
2
|
+
/**
|
|
3
|
+
* `req.context` key under which `validateSubmission` stashes the loaded form's poll config
|
|
4
|
+
* (`null` when the form has none) so the voted-cookie `afterChange` hook can skip a second
|
|
5
|
+
* form fetch on the same request.
|
|
6
|
+
*/
|
|
7
|
+
const POLL_CONTEXT_KEY = "formBuilderPollConfig";
|
|
8
|
+
/** Name of the httpOnly voted cookie for a form: `fb-voted-{formId}`. */
|
|
9
|
+
const votedCookieName = (formId) => `fb-voted-${formId}`;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a request's `Cookie` header carries the voted marker for a form. For SSR hosts: read
|
|
12
|
+
* the header (e.g. Next's `(await headers()).get('cookie')`) and pass the result to `<Poll hasVoted>`.
|
|
13
|
+
* The cookie is httpOnly (set server-side when the plugin's `poll.votedCookie` option is on), so
|
|
14
|
+
* this server read is the only way to consume it.
|
|
15
|
+
*/
|
|
16
|
+
const hasVotedCookie = (cookieHeader, formId) => {
|
|
17
|
+
if (!cookieHeader) return false;
|
|
18
|
+
const name = votedCookieName(formId);
|
|
19
|
+
return cookieHeader.split(";").some((pair) => {
|
|
20
|
+
const eq = pair.indexOf("=");
|
|
21
|
+
return eq !== -1 && pair.slice(0, eq).trim() === name;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { POLL_CONTEXT_KEY, hasVotedCookie, votedCookieName };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=votedCookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"votedCookie.js","names":[],"sources":["../../src/submissions/votedCookie.ts"],"sourcesContent":["/**\n * `req.context` key under which `validateSubmission` stashes the loaded form's poll config\n * (`null` when the form has none) so the voted-cookie `afterChange` hook can skip a second\n * form fetch on the same request.\n */\nexport const POLL_CONTEXT_KEY = 'formBuilderPollConfig'\n\n/** Name of the httpOnly voted cookie for a form: `fb-voted-{formId}`. */\nexport const votedCookieName = (formId: number | string): string => `fb-voted-${formId}`\n\n/**\n * Whether a request's `Cookie` header carries the voted marker for a form. For SSR hosts: read\n * the header (e.g. Next's `(await headers()).get('cookie')`) and pass the result to `<Poll hasVoted>`.\n * The cookie is httpOnly (set server-side when the plugin's `poll.votedCookie` option is on), so\n * this server read is the only way to consume it.\n */\nexport const hasVotedCookie = (\n\tcookieHeader: string | null | undefined,\n\tformId: number | string\n): boolean => {\n\tif (!cookieHeader) {\n\t\treturn false\n\t}\n\tconst name = votedCookieName(formId)\n\treturn cookieHeader.split(';').some((pair) => {\n\t\tconst eq = pair.indexOf('=')\n\t\treturn eq !== -1 && pair.slice(0, eq).trim() === name\n\t})\n}\n"],"mappings":";;;;;;AAKA,MAAa,mBAAmB;;AAGhC,MAAa,mBAAmB,WAAoC,YAAY;;;;;;;AAQhF,MAAa,kBACZ,cACA,WACa;CACb,IAAI,CAAC,cACJ,OAAO;CAER,MAAM,OAAO,gBAAgB,MAAM;CACnC,OAAO,aAAa,MAAM,GAAG,EAAE,MAAM,SAAS;EAC7C,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,OAAO,OAAO,MAAM,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,MAAM;CAClD,CAAC;AACF"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { keys } from "./keys.js";
|
|
2
|
+
//#region src/translations/de.ts
|
|
3
|
+
/**
|
|
4
|
+
* German values, keyed by the typed constants in `keys.ts` so the two stay in
|
|
5
|
+
* lockstep. The `Record<TranslationKey, string>` annotation makes a missing or
|
|
6
|
+
* unknown key a type error. `translations/index.ts` nests these for Payload.
|
|
7
|
+
*/
|
|
8
|
+
const de = {
|
|
9
|
+
[keys.fieldTitle]: "Titel",
|
|
10
|
+
[keys.fieldTypeText]: "Text",
|
|
11
|
+
[keys.fieldTypeTextarea]: "Textarea",
|
|
12
|
+
[keys.fieldTypeEmail]: "E-Mail",
|
|
13
|
+
[keys.fieldTypeNumber]: "Zahl",
|
|
14
|
+
[keys.fieldTypeSelect]: "Auswahl",
|
|
15
|
+
[keys.fieldTypeCheckbox]: "Checkbox",
|
|
16
|
+
[keys.fieldTypeDate]: "Datum",
|
|
17
|
+
[keys.configOptions]: "Optionen",
|
|
18
|
+
[keys.configOption]: "Option",
|
|
19
|
+
[keys.configOptionLabel]: "Optionsbezeichnung",
|
|
20
|
+
[keys.configOptionValue]: "Optionswert",
|
|
21
|
+
[keys.validationRequired]: "Dieses Feld ist erforderlich",
|
|
22
|
+
[keys.validationEmail]: "Gib eine gültige E-Mail-Adresse ein",
|
|
23
|
+
[keys.validationNumber]: "Gib eine gültige Zahl ein",
|
|
24
|
+
[keys.validationDate]: "Gib ein gültiges Datum ein",
|
|
25
|
+
[keys.validationSelect]: "Wähle eine gültige Option",
|
|
26
|
+
[keys.validationEmailFieldUnknown]: "Wähle ein bestehendes E-Mail-Feld dieses Formulars",
|
|
27
|
+
[keys.formatYes]: "Ja",
|
|
28
|
+
[keys.formatNo]: "Nein",
|
|
29
|
+
[keys.configName]: "Name",
|
|
30
|
+
[keys.configLabel]: "Bezeichnung",
|
|
31
|
+
[keys.configRequired]: "Erforderlich",
|
|
32
|
+
[keys.configWidth]: "Breite",
|
|
33
|
+
[keys.configPlaceholder]: "Platzhalter",
|
|
34
|
+
[keys.configDescription]: "Beschreibung",
|
|
35
|
+
[keys.configVisibleWhen]: "Dieses Feld anzeigen, wenn",
|
|
36
|
+
[keys.configValidateWhen]: "Dieses Feld nur validieren, wenn",
|
|
37
|
+
[keys.submissionAnswers]: "Antworten",
|
|
38
|
+
[keys.submissionNoAnswers]: "Keine Antworten",
|
|
39
|
+
[keys.ruleMinLength]: "Minimale Länge",
|
|
40
|
+
[keys.ruleMaxLength]: "Maximale Länge",
|
|
41
|
+
[keys.ruleMin]: "Minimum",
|
|
42
|
+
[keys.ruleMax]: "Maximum",
|
|
43
|
+
[keys.ruleMinDate]: "Frühestes Datum",
|
|
44
|
+
[keys.ruleMaxDate]: "Spätestes Datum",
|
|
45
|
+
[keys.rulePattern]: "Muster",
|
|
46
|
+
[keys.ruleEmail]: "E-Mail",
|
|
47
|
+
[keys.ruleUrl]: "URL",
|
|
48
|
+
[keys.ruleOneOf]: "Wert aus Liste",
|
|
49
|
+
[keys.ruleMatchesField]: "Stimmt mit Feld überein",
|
|
50
|
+
[keys.ruleNotAlreadySubmitted]: "Noch nicht übermittelt",
|
|
51
|
+
[keys.ruleMinLengthMessage]: "Muss mindestens {min} Zeichen lang sein",
|
|
52
|
+
[keys.ruleMaxLengthMessage]: "Darf höchstens {max} Zeichen lang sein",
|
|
53
|
+
[keys.ruleMinMessage]: "Muss mindestens {min} betragen",
|
|
54
|
+
[keys.ruleMaxMessage]: "Darf höchstens {max} betragen",
|
|
55
|
+
[keys.ruleMinDateMessage]: "Muss am oder nach dem {min} liegen",
|
|
56
|
+
[keys.ruleMaxDateMessage]: "Muss am oder vor dem {max} liegen",
|
|
57
|
+
[keys.rulePatternMessage]: "Ungültiges Format",
|
|
58
|
+
[keys.ruleEmailMessage]: "Gib eine gültige E-Mail-Adresse ein",
|
|
59
|
+
[keys.ruleUrlMessage]: "Gib eine gültige URL ein",
|
|
60
|
+
[keys.ruleOneOfMessage]: "Wähle einen zulässigen Wert",
|
|
61
|
+
[keys.ruleMatchesFieldMessage]: "Stimmt nicht überein",
|
|
62
|
+
[keys.ruleNotAlreadySubmittedMessage]: "Dieser Wert wurde bereits übermittelt",
|
|
63
|
+
[keys.ruleParamMin]: "Minimum",
|
|
64
|
+
[keys.ruleParamMax]: "Maximum",
|
|
65
|
+
[keys.ruleParamMinDate]: "Frühestes Datum (JJJJ-MM-TT)",
|
|
66
|
+
[keys.ruleParamMaxDate]: "Spätestes Datum (JJJJ-MM-TT)",
|
|
67
|
+
[keys.ruleParamPattern]: "Muster",
|
|
68
|
+
[keys.ruleParamFlags]: "Flags",
|
|
69
|
+
[keys.ruleParamValues]: "Zulässige Werte",
|
|
70
|
+
[keys.ruleParamField]: "Feldname",
|
|
71
|
+
[keys.validationsLabel]: "Validierungsregeln",
|
|
72
|
+
[keys.validationMessageLabel]: "Benutzerdefinierte Nachricht",
|
|
73
|
+
[keys.validationSeverityLabel]: "Schweregrad",
|
|
74
|
+
[keys.validationSeverityError]: "Fehler",
|
|
75
|
+
[keys.validationSeverityWarning]: "Warnung",
|
|
76
|
+
[keys.conditionAddCondition]: "Bedingung hinzufügen",
|
|
77
|
+
[keys.conditionAddOr]: "\"Oder\"-Gruppe hinzufügen",
|
|
78
|
+
[keys.conditionAnd]: "Und",
|
|
79
|
+
[keys.conditionOr]: "Oder",
|
|
80
|
+
[keys.conditionRemove]: "Entfernen",
|
|
81
|
+
[keys.conditionNoFields]: "Füge diesem Formular benannte Felder hinzu, um eine Bedingung zu erstellen.",
|
|
82
|
+
[keys.conditionEmpty]: "Keine Bedingungen. Dieses Feld wird immer angezeigt.",
|
|
83
|
+
[keys.conditionSelectField]: "Feld auswählen",
|
|
84
|
+
[keys.conditionTrue]: "Wahr",
|
|
85
|
+
[keys.conditionFalse]: "Falsch",
|
|
86
|
+
[keys.configHidden]: "Ausgeblendet (wird erfasst, aber nicht angezeigt)",
|
|
87
|
+
[keys.tabFields]: "Felder",
|
|
88
|
+
[keys.tabFlow]: "Ablauf",
|
|
89
|
+
[keys.tabActions]: "Aktionen",
|
|
90
|
+
[keys.tabField]: "Feld",
|
|
91
|
+
[keys.tabValidation]: "Validierung",
|
|
92
|
+
[keys.tabAdvanced]: "Erweitert",
|
|
93
|
+
[keys.fieldTypeCalculation]: "Berechnung",
|
|
94
|
+
[keys.configExpression]: "Ausdruck (JSON)",
|
|
95
|
+
[keys.configExpressionDescription]: "Ausdrucksbaum aus Knoten {\"type\":\"lit\"|\"ref\"|\"op\"|\"neg\"|\"fn\"|\"weight\"}. Beispiel: {\"type\":\"op\",\"op\":\"+\",\"left\":{\"type\":\"ref\",\"field\":\"qty\"},\"right\":{\"type\":\"lit\",\"value\":10}}",
|
|
96
|
+
[keys.configCalcDisplay]: "Berechneten Wert anzeigen",
|
|
97
|
+
[keys.validationCalcExpressionInvalid]: "Gib einen gültigen Berechnungsausdruck ein",
|
|
98
|
+
[keys.presentationPage]: "Seite",
|
|
99
|
+
[keys.presentationModal]: "Modal",
|
|
100
|
+
[keys.presentationDrawer]: "Seitenpanel",
|
|
101
|
+
[keys.presentationInline]: "Inline",
|
|
102
|
+
[keys.actionEmailTeam]: "E-Mail ans Team",
|
|
103
|
+
[keys.actionConfirmation]: "Bestätigungs-E-Mail",
|
|
104
|
+
[keys.actionSignedWebhook]: "Signierter Webhook",
|
|
105
|
+
[keys.actionConfigTo]: "An",
|
|
106
|
+
[keys.actionConfigSubject]: "Betreff",
|
|
107
|
+
[keys.actionConfigBody]: "Nachrichtentext",
|
|
108
|
+
[keys.actionConfigBodyDescription]: "Unterstützt {{ fieldName|fallback }}-Platzhalter, {{*}} für alle Antworten als Zeilen und {{*:table}} für alle Antworten als Tabelle.",
|
|
109
|
+
[keys.actionConfigToField]: "Name des E-Mail-Feldes",
|
|
110
|
+
[keys.actionConfigToFieldDescription]: "Das E-Mail-Feld dieses Formulars, an das die Bestätigung gesendet wird.",
|
|
111
|
+
[keys.actionConfigUrl]: "URL",
|
|
112
|
+
[keys.actionConfigUrlDescription]: "Der Endpunkt, der für jede Übermittlung einen signierten JSON-POST erhält.",
|
|
113
|
+
[keys.actionConfigSecret]: "Geheimer Schlüssel",
|
|
114
|
+
[keys.actionConfigSecretDescription]: "HMAC-Schlüssel für den X-Form-Signature-Header, der mit dem Empfänger geteilt wird.",
|
|
115
|
+
[keys.validationUrlInvalid]: "Gib eine gültige http- oder https-URL ein",
|
|
116
|
+
[keys.configActions]: "Aktionen",
|
|
117
|
+
[keys.consentSourceStatic]: "Statischer Text",
|
|
118
|
+
[keys.consentSourcePageReference]: "Seitenverweis",
|
|
119
|
+
[keys.consentConfigLabel]: "Bezeichnung",
|
|
120
|
+
[keys.consentConfigUrl]: "URL",
|
|
121
|
+
[keys.consentConfigVersion]: "Version",
|
|
122
|
+
[keys.consentConfigRelationTo]: "Sammlung",
|
|
123
|
+
[keys.consentConfigDocId]: "Dokument-ID",
|
|
124
|
+
[keys.consentConfigUrlField]: "URL-Feld",
|
|
125
|
+
[keys.consentConfigCaptureVersion]: "Veröffentlichte Version erfassen",
|
|
126
|
+
[keys.fieldTypeConsent]: "Einwilligung",
|
|
127
|
+
[keys.consentConfigStatement]: "Erklärung",
|
|
128
|
+
[keys.consentConfigSource]: "Quelle",
|
|
129
|
+
[keys.consentConfigSourceConfig]: "Quelleinstellungen",
|
|
130
|
+
[keys.consentConfigOptional]: "Optional (z. B. Marketing)",
|
|
131
|
+
[keys.resultsResponses]: "Antworten",
|
|
132
|
+
[keys.resultsNoResponses]: "Noch keine Antworten",
|
|
133
|
+
[keys.resultsTruncated]: "Zeigt eine Stichprobe der Antworten",
|
|
134
|
+
[keys.pollGroup]: "Umfrage",
|
|
135
|
+
[keys.pollEnabled]: "Umfrageergebnisse aktivieren",
|
|
136
|
+
[keys.pollResultsField]: "Ergebnisfeld (technischer Name)",
|
|
137
|
+
[keys.pollResultsFieldDescription]: "Feld, dessen aggregierte Ergebnisse öffentlich sind. Verwende ein Auswahlfeld, niemals ein Freitext- oder personenbezogenes Feld.",
|
|
138
|
+
[keys.pollResultsVisibility]: "Sichtbarkeit der Ergebnisse",
|
|
139
|
+
[keys.pollVisibilityAfterVote]: "Nach der Abstimmung",
|
|
140
|
+
[keys.pollVisibilityAfterClose]: "Nach Ende der Umfrage",
|
|
141
|
+
[keys.pollClosesAt]: "Endet am",
|
|
142
|
+
[keys.pollClosed]: "Diese Umfrage ist beendet.",
|
|
143
|
+
[keys.pollResultsAfterClose]: "Die Ergebnisse werden nach Ende der Umfrage angezeigt.",
|
|
144
|
+
[keys.pollOptionSource]: "Optionsquelle",
|
|
145
|
+
[keys.pollOptionSourceDescription]: "Befüllt die Auswahlmöglichkeiten des Ergebnisfeldes mit App-Daten anstelle manuell erstellter Optionen.",
|
|
146
|
+
[keys.pollSourceConfig]: "Quelleinstellungen",
|
|
147
|
+
[keys.pollOutcome]: "Ergebnis",
|
|
148
|
+
[keys.pollWinningValue]: "Siegerwert",
|
|
149
|
+
[keys.pollResolvedAt]: "Entschieden am",
|
|
150
|
+
[keys.pollOptionsUnavailable]: "Umfrageoptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.",
|
|
151
|
+
[keys.pollFinalResult]: "Endergebnis",
|
|
152
|
+
[keys.resultsWinner]: "Gewinner",
|
|
153
|
+
[keys.validationFileMissing]: "Datei hochladen",
|
|
154
|
+
[keys.validationFileMimeType]: "Dateityp nicht erlaubt",
|
|
155
|
+
[keys.validationFileTooLarge]: "Datei ist zu groß",
|
|
156
|
+
[keys.fieldTypeFile]: "Datei-Upload",
|
|
157
|
+
[keys.fileConfigMimeTypes]: "Erlaubte Dateitypen",
|
|
158
|
+
[keys.fileConfigMaxSize]: "Maximale Größe (Bytes)",
|
|
159
|
+
[keys.fileConfigMaxSizeDescription]: "Dateien, die größer sind, werden abgelehnt.",
|
|
160
|
+
[keys.fileTooLarge]: "Datei ist zu groß (max. {max})",
|
|
161
|
+
[keys.fileUploadMisconfigured]: "Datei-Uploads sind für dieses Formular nicht konfiguriert",
|
|
162
|
+
[keys.fileHintAccepted]: "Akzeptiert: {types}",
|
|
163
|
+
[keys.fileHintMaxSize]: "Max. Größe: {max}",
|
|
164
|
+
[keys.spamRateLimited]: "Du hast zu viele Anfragen gesendet. Bitte versuche es später erneut.",
|
|
165
|
+
[keys.spamRejected]: "Deine Übermittlung konnte nicht verarbeitet werden.",
|
|
166
|
+
[keys.spamCaptchaFailed]: "Captcha-Überprüfung fehlgeschlagen. Bitte versuche es erneut.",
|
|
167
|
+
[keys.collectionFormSingular]: "Formular",
|
|
168
|
+
[keys.collectionFormPlural]: "Formulare",
|
|
169
|
+
[keys.collectionSubmissionSingular]: "Übermittlung",
|
|
170
|
+
[keys.collectionSubmissionPlural]: "Übermittlungen",
|
|
171
|
+
[keys.statusComplete]: "Vollständig",
|
|
172
|
+
[keys.statusPartial]: "Unvollständig",
|
|
173
|
+
[keys.fieldTypeRepeater]: "Wiederholungsfeld",
|
|
174
|
+
[keys.configMinRows]: "Minimale Zeilenanzahl",
|
|
175
|
+
[keys.configMaxRows]: "Maximale Zeilenanzahl",
|
|
176
|
+
[keys.configAddLabel]: "Beschriftung der Schaltfläche zum Hinzufügen",
|
|
177
|
+
[keys.configSubFields]: "Unterfelder",
|
|
178
|
+
[keys.validationRepeaterMin]: "Füge mindestens {min} Zeile(n) hinzu",
|
|
179
|
+
[keys.validationRepeaterMax]: "Entferne Zeilen, um {max} nicht zu überschreiten",
|
|
180
|
+
[keys.repeaterAddRow]: "Zeile hinzufügen",
|
|
181
|
+
[keys.repeaterRemoveRow]: "Entfernen",
|
|
182
|
+
[keys.repeaterRow]: "Zeile {n}",
|
|
183
|
+
[keys.repeaterRowCount]: "{count} Zeile(n)",
|
|
184
|
+
[keys.submissionConsent]: "Einwilligung",
|
|
185
|
+
[keys.submissionDetails]: "Details zur Übermittlung",
|
|
186
|
+
[keys.submissionConsentAgreed]: "Zugestimmt",
|
|
187
|
+
[keys.submissionConsentDeclined]: "Abgelehnt",
|
|
188
|
+
[keys.submissionMetaReceivedAt]: "Empfangen am",
|
|
189
|
+
[keys.submissionMetaIp]: "IP-Adresse",
|
|
190
|
+
[keys.submissionMetaUserAgent]: "User-Agent",
|
|
191
|
+
[keys.submissionMetaCaptcha]: "Captcha",
|
|
192
|
+
[keys.flowDescription]: "Nur für mehrstufige Formulare nötig: Felder zu Schritten gruppieren und den Ablauf dazwischen festlegen. Leer lassen, um das Formular als einzelne Seite anzuzeigen.",
|
|
193
|
+
[keys.flowStepFallbackTitle]: "Schritt {n}",
|
|
194
|
+
[keys.flowFieldInStep]: "in {step}",
|
|
195
|
+
[keys.flowUnassigned]: "In keinem Schritt",
|
|
196
|
+
[keys.flowAssignToStep]: "Zu Schritt hinzufügen",
|
|
197
|
+
[keys.flowNextTerminal]: "Ende des Formulars",
|
|
198
|
+
[keys.fieldTypeMessage]: "Nachricht",
|
|
199
|
+
[keys.configContent]: "Inhalt",
|
|
200
|
+
[keys.tabResponse]: "Antwort",
|
|
201
|
+
[keys.responseType]: "Nach dem Absenden",
|
|
202
|
+
[keys.responseTypeMessage]: "Eine Nachricht anzeigen",
|
|
203
|
+
[keys.responseTypeRedirect]: "Zu einer URL weiterleiten",
|
|
204
|
+
[keys.responseMessage]: "Nachricht",
|
|
205
|
+
[keys.responseRedirect]: "Weiterleitung",
|
|
206
|
+
[keys.responseUrl]: "URL",
|
|
207
|
+
[keys.responseSubmitLabel]: "Beschriftung der Absenden-Schaltfläche",
|
|
208
|
+
[keys.tabDisplay]: "Anzeige",
|
|
209
|
+
[keys.displayShowTitle]: "Titel anzeigen",
|
|
210
|
+
[keys.displayTitle]: "Anzeigetitel",
|
|
211
|
+
[keys.displayIntro]: "Einleitung",
|
|
212
|
+
[keys.formBack]: "Zurück",
|
|
213
|
+
[keys.formNext]: "Weiter",
|
|
214
|
+
[keys.formSubmit]: "Absenden"
|
|
215
|
+
};
|
|
216
|
+
//#endregion
|
|
217
|
+
export { de };
|
|
218
|
+
|
|
219
|
+
//# sourceMappingURL=de.js.map
|