@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
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { localizedIf } from "../fields/localizedIf.js";
|
|
1
2
|
import { keys } from "../translations/keys.js";
|
|
2
|
-
import {
|
|
3
|
+
import { labelForKey } from "../translations/server.js";
|
|
4
|
+
import { validateUrl } from "../validation/validateUrl.js";
|
|
5
|
+
import { normalizeCalc } from "../calc/normalizeCalc.js";
|
|
3
6
|
import { isLoggedIn } from "../plugin/access.js";
|
|
4
7
|
import { buildActionBlocks } from "../actions/buildActionBlocks.js";
|
|
5
8
|
import { resolveFormResultsRequest } from "../aggregation/resolveResultsRequest.js";
|
|
6
|
-
import { normalizeCalc } from "../calc/normalizeCalc.js";
|
|
7
9
|
import { buildConditionTypeMap } from "../conditions/conditionType.js";
|
|
8
10
|
import { normalizeFormConditions } from "../conditions/normalizeConditions.js";
|
|
9
11
|
import { buildFieldBlocks } from "../fields/buildFieldBlocks.js";
|
|
10
12
|
import { normalizeFlow } from "../flow/normalizeFlow.js";
|
|
11
|
-
import {
|
|
13
|
+
import { buildPollOptionSourceFields } from "../poll/buildPollOptionSourceFields.js";
|
|
12
14
|
import { ValidationError } from "payload";
|
|
13
15
|
//#region src/collections/forms.ts
|
|
14
16
|
const FORMS_SLUG = "forms";
|
|
@@ -36,14 +38,27 @@ const providedFlowStepCount = (raw) => {
|
|
|
36
38
|
const steps = raw.steps;
|
|
37
39
|
return Array.isArray(steps) ? steps.length : 0;
|
|
38
40
|
};
|
|
39
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Stamp the configured uploads collection slug onto every `file` block (including repeater
|
|
43
|
+
* sub-fields) on each save. The block carries a hidden `uploadsCollection` field so the slug
|
|
44
|
+
* reaches the client renderer through the form document; the server always overwrites it from
|
|
45
|
+
* plugin config, so the stored value is never author- or client-controlled.
|
|
46
|
+
*/
|
|
47
|
+
const stampFileCollections = (rows, slug) => {
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
if (row.blockType === "file") row.uploadsCollection = slug;
|
|
50
|
+
const subFields = row.subFields;
|
|
51
|
+
if (Array.isArray(subFields)) stampFileCollections(subFields, slug);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const buildFormsCollection = ({ overrides, registry, ruleRegistry, consentRegistry, actionRegistry = /* @__PURE__ */ new Map(), localizeContent = true, uploadsCollectionSlug, resultsAccess, pollSourceRegistry }) => {
|
|
40
55
|
const conditionTypes = buildConditionTypeMap(registry);
|
|
41
56
|
const FLOW_BUILDER_REF = "@10x-media/form-builder/client#FlowBuilder";
|
|
42
57
|
const beforeValidate = ({ data, req }) => {
|
|
43
|
-
if (data && typeof data.defaultPresentation === "string" && !presentationRegistry.has(data.defaultPresentation)) data.defaultPresentation = DEFAULT_PRESENTATION_NAME;
|
|
44
58
|
if (data && Array.isArray(data.fields)) {
|
|
45
59
|
const normalized = normalizeFormConditions(data.fields, conditionTypes);
|
|
46
60
|
for (const field of normalized) if ("expression" in field) field.expression = normalizeCalc(field.expression);
|
|
61
|
+
if (uploadsCollectionSlug) stampFileCollections(normalized, uploadsCollectionSlug);
|
|
47
62
|
data.fields = normalized;
|
|
48
63
|
const fieldNames = normalized.map((field) => typeof field.name === "string" ? field.name : void 0).filter((name) => name !== void 0);
|
|
49
64
|
const normalizedFlow = normalizeFlow(data.flow, fieldNames);
|
|
@@ -51,13 +66,146 @@ const buildFormsCollection = ({ overrides, registry, ruleRegistry, consentRegist
|
|
|
51
66
|
collection: FORMS_SLUG,
|
|
52
67
|
errors: [{
|
|
53
68
|
path: "flow",
|
|
54
|
-
message: "A flow needs at least two steps
|
|
69
|
+
message: "A flow needs at least two steps. Add another step or remove the flow."
|
|
55
70
|
}]
|
|
56
71
|
}, req.t);
|
|
57
72
|
data.flow = normalizedFlow;
|
|
58
73
|
}
|
|
59
74
|
return data;
|
|
60
75
|
};
|
|
76
|
+
const fieldsField = {
|
|
77
|
+
name: "fields",
|
|
78
|
+
type: "blocks",
|
|
79
|
+
blocks: buildFieldBlocks({
|
|
80
|
+
registry,
|
|
81
|
+
ruleRegistry,
|
|
82
|
+
consentRegistry,
|
|
83
|
+
localize: localizeContent
|
|
84
|
+
})
|
|
85
|
+
};
|
|
86
|
+
const flowField = {
|
|
87
|
+
name: "flow",
|
|
88
|
+
type: "json",
|
|
89
|
+
validate: validateFlow,
|
|
90
|
+
admin: { components: { Field: {
|
|
91
|
+
path: FLOW_BUILDER_REF,
|
|
92
|
+
clientProps: { conditionTypes }
|
|
93
|
+
} } },
|
|
94
|
+
typescriptSchema: [() => ({
|
|
95
|
+
type: "object",
|
|
96
|
+
required: ["steps"],
|
|
97
|
+
additionalProperties: false,
|
|
98
|
+
properties: { steps: {
|
|
99
|
+
type: "array",
|
|
100
|
+
items: {
|
|
101
|
+
type: "object",
|
|
102
|
+
required: ["id"],
|
|
103
|
+
additionalProperties: true,
|
|
104
|
+
properties: {
|
|
105
|
+
id: { type: "string" },
|
|
106
|
+
title: { type: "string" },
|
|
107
|
+
fields: {
|
|
108
|
+
type: "array",
|
|
109
|
+
items: { type: "string" }
|
|
110
|
+
},
|
|
111
|
+
next: { type: "string" },
|
|
112
|
+
transitions: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: {
|
|
115
|
+
type: "object",
|
|
116
|
+
required: ["to"],
|
|
117
|
+
additionalProperties: true,
|
|
118
|
+
properties: {
|
|
119
|
+
to: { type: "string" },
|
|
120
|
+
when: {
|
|
121
|
+
type: "object",
|
|
122
|
+
additionalProperties: true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} }
|
|
130
|
+
})]
|
|
131
|
+
};
|
|
132
|
+
const actionsField = {
|
|
133
|
+
name: "actions",
|
|
134
|
+
type: "blocks",
|
|
135
|
+
blocks: buildActionBlocks(actionRegistry),
|
|
136
|
+
label: labelForKey(keys.configActions),
|
|
137
|
+
access: { read: isLoggedIn }
|
|
138
|
+
};
|
|
139
|
+
const responseField = {
|
|
140
|
+
name: "response",
|
|
141
|
+
type: "group",
|
|
142
|
+
fields: [
|
|
143
|
+
{
|
|
144
|
+
name: "type",
|
|
145
|
+
type: "select",
|
|
146
|
+
defaultValue: "message",
|
|
147
|
+
label: labelForKey(keys.responseType),
|
|
148
|
+
admin: { isClearable: false },
|
|
149
|
+
options: [{
|
|
150
|
+
label: labelForKey(keys.responseTypeMessage),
|
|
151
|
+
value: "message"
|
|
152
|
+
}, {
|
|
153
|
+
label: labelForKey(keys.responseTypeRedirect),
|
|
154
|
+
value: "redirect"
|
|
155
|
+
}]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "message",
|
|
159
|
+
type: "richText",
|
|
160
|
+
label: labelForKey(keys.responseMessage),
|
|
161
|
+
admin: { condition: (_data, siblingData) => siblingData?.type !== "redirect" },
|
|
162
|
+
...localizedIf(localizeContent)
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "redirect",
|
|
166
|
+
type: "group",
|
|
167
|
+
label: labelForKey(keys.responseRedirect),
|
|
168
|
+
admin: { condition: (_data, siblingData) => siblingData?.type === "redirect" },
|
|
169
|
+
fields: [{
|
|
170
|
+
name: "url",
|
|
171
|
+
type: "text",
|
|
172
|
+
label: labelForKey(keys.responseUrl),
|
|
173
|
+
validate: validateUrl
|
|
174
|
+
}]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "submitLabel",
|
|
178
|
+
type: "text",
|
|
179
|
+
label: labelForKey(keys.responseSubmitLabel),
|
|
180
|
+
...localizedIf(localizeContent)
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
};
|
|
184
|
+
const displayField = {
|
|
185
|
+
name: "display",
|
|
186
|
+
type: "group",
|
|
187
|
+
fields: [
|
|
188
|
+
{
|
|
189
|
+
name: "showTitle",
|
|
190
|
+
type: "checkbox",
|
|
191
|
+
defaultValue: false,
|
|
192
|
+
label: labelForKey(keys.displayShowTitle)
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "title",
|
|
196
|
+
type: "text",
|
|
197
|
+
label: labelForKey(keys.displayTitle),
|
|
198
|
+
admin: { condition: (_data, siblingData) => Boolean(siblingData?.showTitle) },
|
|
199
|
+
...localizedIf(localizeContent)
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "intro",
|
|
203
|
+
type: "richText",
|
|
204
|
+
label: labelForKey(keys.displayIntro),
|
|
205
|
+
...localizedIf(localizeContent)
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
61
209
|
const defaultFields = [
|
|
62
210
|
{
|
|
63
211
|
name: "title",
|
|
@@ -66,90 +214,104 @@ const buildFormsCollection = ({ overrides, registry, ruleRegistry, consentRegist
|
|
|
66
214
|
label: labelForKey(keys.fieldTitle)
|
|
67
215
|
},
|
|
68
216
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
217
|
+
type: "tabs",
|
|
218
|
+
tabs: [
|
|
219
|
+
{
|
|
220
|
+
label: labelForKey(keys.tabFields),
|
|
221
|
+
fields: [fieldsField]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: labelForKey(keys.tabDisplay),
|
|
225
|
+
fields: [displayField]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: labelForKey(keys.tabFlow),
|
|
229
|
+
fields: [flowField]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: labelForKey(keys.tabActions),
|
|
233
|
+
fields: [actionsField]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: labelForKey(keys.tabResponse),
|
|
237
|
+
fields: [responseField]
|
|
238
|
+
}
|
|
239
|
+
]
|
|
72
240
|
},
|
|
73
241
|
{
|
|
74
|
-
name: "
|
|
75
|
-
type: "
|
|
76
|
-
|
|
77
|
-
admin: {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
id: { type: "string" },
|
|
93
|
-
title: { type: "string" },
|
|
94
|
-
fields: {
|
|
95
|
-
type: "array",
|
|
96
|
-
items: { type: "string" }
|
|
97
|
-
},
|
|
98
|
-
next: { type: "string" },
|
|
99
|
-
transitions: {
|
|
100
|
-
type: "array",
|
|
101
|
-
items: {
|
|
102
|
-
type: "object",
|
|
103
|
-
required: ["to"],
|
|
104
|
-
additionalProperties: true,
|
|
105
|
-
properties: {
|
|
106
|
-
to: { type: "string" },
|
|
107
|
-
when: {
|
|
108
|
-
type: "object",
|
|
109
|
-
additionalProperties: true
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
242
|
+
name: "poll",
|
|
243
|
+
type: "group",
|
|
244
|
+
label: labelForKey(keys.pollGroup),
|
|
245
|
+
admin: { position: "sidebar" },
|
|
246
|
+
fields: [
|
|
247
|
+
{
|
|
248
|
+
name: "enabled",
|
|
249
|
+
type: "checkbox",
|
|
250
|
+
defaultValue: false,
|
|
251
|
+
label: labelForKey(keys.pollEnabled)
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "resultsField",
|
|
255
|
+
type: "text",
|
|
256
|
+
label: labelForKey(keys.pollResultsField),
|
|
257
|
+
admin: {
|
|
258
|
+
description: labelForKey(keys.pollResultsFieldDescription),
|
|
259
|
+
condition: (_data, siblingData) => Boolean(siblingData?.enabled)
|
|
115
260
|
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "resultsVisibility",
|
|
264
|
+
type: "select",
|
|
265
|
+
defaultValue: "afterVote",
|
|
266
|
+
label: labelForKey(keys.pollResultsVisibility),
|
|
267
|
+
admin: {
|
|
268
|
+
isClearable: false,
|
|
269
|
+
condition: (_data, siblingData) => Boolean(siblingData?.enabled)
|
|
270
|
+
},
|
|
271
|
+
options: [{
|
|
272
|
+
label: labelForKey(keys.pollVisibilityAfterVote),
|
|
273
|
+
value: "afterVote"
|
|
274
|
+
}, {
|
|
275
|
+
label: labelForKey(keys.pollVisibilityAfterClose),
|
|
276
|
+
value: "afterClose"
|
|
277
|
+
}]
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "closesAt",
|
|
281
|
+
type: "date",
|
|
282
|
+
label: labelForKey(keys.pollClosesAt),
|
|
283
|
+
admin: {
|
|
284
|
+
date: { pickerAppearance: "dayAndTime" },
|
|
285
|
+
condition: (_data, siblingData) => Boolean(siblingData?.enabled)
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
...buildPollOptionSourceFields(pollSourceRegistry ?? /* @__PURE__ */ new Map()),
|
|
289
|
+
{
|
|
290
|
+
name: "outcome",
|
|
291
|
+
type: "group",
|
|
292
|
+
label: labelForKey(keys.pollOutcome),
|
|
293
|
+
admin: { condition: (_data, siblingData) => Boolean(siblingData?.enabled) },
|
|
294
|
+
fields: [{
|
|
295
|
+
name: "winningValue",
|
|
296
|
+
type: "text",
|
|
297
|
+
label: labelForKey(keys.pollWinningValue),
|
|
298
|
+
admin: { readOnly: true },
|
|
299
|
+
access: {
|
|
300
|
+
create: () => false,
|
|
301
|
+
update: () => false
|
|
302
|
+
}
|
|
303
|
+
}, {
|
|
304
|
+
name: "resolvedAt",
|
|
305
|
+
type: "date",
|
|
306
|
+
label: labelForKey(keys.pollResolvedAt),
|
|
307
|
+
admin: { readOnly: true },
|
|
308
|
+
access: {
|
|
309
|
+
create: () => false,
|
|
310
|
+
update: () => false
|
|
311
|
+
}
|
|
312
|
+
}]
|
|
313
|
+
}
|
|
314
|
+
]
|
|
153
315
|
}
|
|
154
316
|
];
|
|
155
317
|
const defaultEndpoints = [{
|
|
@@ -162,7 +324,8 @@ const buildFormsCollection = ({ overrides, registry, ruleRegistry, consentRegist
|
|
|
162
324
|
formId: req.routeParams?.id,
|
|
163
325
|
field,
|
|
164
326
|
isAuthed: Boolean(req.user),
|
|
165
|
-
req
|
|
327
|
+
req,
|
|
328
|
+
access: resultsAccess
|
|
166
329
|
});
|
|
167
330
|
return Response.json(body, { status });
|
|
168
331
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype Field,\n\ttype PayloadRequest,\n\tValidationError,\n} from 'payload'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { ActionRegistry } from '../actions/registry'\nimport { resolveFormResultsRequest } from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\n\nexport const FORMS_SLUG = 'forms'\n\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry?: ConsentSourceRegistry\n\tpresentationRegistry?: PresentationDescriptorRegistry\n\tactionRegistry?: ActionRegistry\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry = new Map(Object.entries(defaultPresentationDescriptors)),\n\tactionRegistry = new Map(),\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (\n\t\t\tdata &&\n\t\t\ttypeof data.defaultPresentation === 'string' &&\n\t\t\t!presentationRegistry.has(data.defaultPresentation)\n\t\t) {\n\t\t\tdata.defaultPresentation = DEFAULT_PRESENTATION_NAME\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\tconst fieldNames = normalized\n\t\t\t\t.map((field: FieldRow) => (typeof field.name === 'string' ? field.name : undefined))\n\t\t\t\t.filter((name): name is string => name !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldNames)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t'A flow needs at least two steps with unique, non-empty IDs. Add another step or remove the flow.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t}\n\t\treturn data\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'title', type: 'text', required: true, label: labelForKey(keys.fieldTitle) },\n\t\t{\n\t\t\tname: 'fields',\n\t\t\ttype: 'blocks',\n\t\t\tblocks: buildFieldBlocks(registry, ruleRegistry, consentRegistry),\n\t\t},\n\t\t{\n\t\t\tname: 'flow',\n\t\t\ttype: 'json',\n\t\t\tvalidate: validateFlow,\n\t\t\tadmin: {\n\t\t\t\tcomponents: {\n\t\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes } },\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\t\ttypescriptSchema: [\n\t\t\t\t() => ({\n\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\trequired: ['steps'],\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tsteps: {\n\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\t\tnext: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: 'actions',\n\t\t\ttype: 'blocks',\n\t\t\tblocks: buildActionBlocks(actionRegistry),\n\t\t\tlabel: labelForKey(keys.configActions),\n\t\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t\t// action config must never be exposed to anonymous callers.\n\t\t\taccess: { read: isLoggedIn },\n\t\t},\n\t\t{\n\t\t\tname: 'defaultPresentation',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: DEFAULT_PRESENTATION_NAME,\n\t\t\toptions: [...presentationRegistry.values()].map((descriptor) => ({\n\t\t\t\tlabel: labelFor(descriptor.label),\n\t\t\t\tvalue: descriptor.name,\n\t\t\t})),\n\t\t\tlabel: labelForKey(keys.configDefaultPresentation),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'showResults',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: false,\n\t\t\tlabel: labelForKey(keys.configShowResults),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'resultsField',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.configResultsField),\n\t\t\tadmin: {\n\t\t\t\tposition: 'sidebar',\n\t\t\t\tdescription:\n\t\t\t\t\t'Field whose aggregate results are public when \"Show results publicly\" is on. Use a choice field, never a free-text or PII field.',\n\t\t\t\tcondition: (data) => Boolean(data?.showResults),\n\t\t\t},\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: { group: 'Forms', useAsTitle: 'title', ...(overrides?.admin ?? {}) },\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype Field,\n\ttype PayloadRequest,\n\tValidationError,\n} from 'payload'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { ActionRegistry } from '../actions/registry'\nimport {\n\ttype FormResultsAccess,\n\tresolveFormResultsRequest,\n} from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport { localizedIf } from '../fields/localizedIf'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport { buildPollOptionSourceFields } from '../poll/buildPollOptionSourceFields'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { validateUrl } from '../validation/validateUrl'\n\nexport const FORMS_SLUG = 'forms'\n\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\n/**\n * Stamp the configured uploads collection slug onto every `file` block (including repeater\n * sub-fields) on each save. The block carries a hidden `uploadsCollection` field so the slug\n * reaches the client renderer through the form document; the server always overwrites it from\n * plugin config, so the stored value is never author- or client-controlled.\n */\nconst stampFileCollections = (rows: FieldRow[], slug: string): void => {\n\tfor (const row of rows) {\n\t\tif (row.blockType === 'file') {\n\t\t\t;(row as { uploadsCollection?: string }).uploadsCollection = slug\n\t\t}\n\t\tconst subFields = (row as { subFields?: unknown }).subFields\n\t\tif (Array.isArray(subFields)) {\n\t\t\tstampFileCollections(subFields as FieldRow[], slug)\n\t\t}\n\t}\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry?: ConsentSourceRegistry\n\tactionRegistry?: ActionRegistry\n\tlocalizeContent?: boolean\n\t/** The host-owned uploads collection slug from plugin config; absent when uploads are disabled. */\n\tuploadsCollectionSlug?: string\n\t/** Host seam gating anonymous results reads (plugin option `results.access`). */\n\tresultsAccess?: FormResultsAccess\n\t/** Registered poll option sources (plugin option `poll.sources`); empty registry means no source fields. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tactionRegistry = new Map(),\n\tlocalizeContent = true,\n\tuploadsCollectionSlug,\n\tresultsAccess,\n\tpollSourceRegistry,\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (uploadsCollectionSlug) {\n\t\t\t\tstampFileCollections(normalized, uploadsCollectionSlug)\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\tconst fieldNames = normalized\n\t\t\t\t.map((field: FieldRow) => (typeof field.name === 'string' ? field.name : undefined))\n\t\t\t\t.filter((name): name is string => name !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldNames)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage: 'A flow needs at least two steps. Add another step or remove the flow.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t}\n\t\treturn data\n\t}\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tlocalize: localizeContent,\n\t\t}),\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\t// What the visitor sees after a successful submit. Publicly readable (unlike actions): the\n\t// client renderer needs message/redirect/submitLabel. `type`/`url` are behavior, never\n\t// localized; `message`/`submitLabel` are visitor-facing content and follow `localizeContent`.\n\t// `type` is defaulted and not clearable rather than `required`: a required member would make\n\t// the whole group required in generated types, breaking typed `payload.create` calls that\n\t// omit `response`. Consumers treat a missing type as 'message'.\n\tconst responseField: Field = {\n\t\tname: 'response',\n\t\ttype: 'group',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'message',\n\t\t\t\tlabel: labelForKey(keys.responseType),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeMessage), value: 'message' },\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeRedirect), value: 'redirect' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'message',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.responseMessage),\n\t\t\t\t// Unset type (docs predating this field) means 'message', matching the client fallback.\n\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.type !== 'redirect' },\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'redirect',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.responseRedirect),\n\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.type === 'redirect' },\n\t\t\t\tfields: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'url',\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\tlabel: labelForKey(keys.responseUrl),\n\t\t\t\t\t\tvalidate: validateUrl,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'submitLabel',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.responseSubmitLabel),\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t],\n\t}\n\n\t// What the visitor sees above the fields, before submit. Its own tab mirrors `response`\n\t// (the after-submit counterpart): both are visitor-facing content, not form behavior, so\n\t// they don't belong inside the Fields tab. `showTitle` gates whether `title` renders at all\n\t// (defaults to false: most forms rely on the host page's own heading and would otherwise\n\t// double up); the admin input for `title` is shown only while `showTitle` is checked, and\n\t// toggling it off hides the input but preserves the stored value, so it comes back with the\n\t// checkbox. `intro` has no such gate: an empty rich text field already renders nothing, so a\n\t// separate visibility flag would be redundant.\n\tconst displayField: Field = {\n\t\tname: 'display',\n\t\ttype: 'group',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'showTitle',\n\t\t\t\ttype: 'checkbox',\n\t\t\t\tdefaultValue: false,\n\t\t\t\tlabel: labelForKey(keys.displayShowTitle),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'title',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.displayTitle),\n\t\t\t\tadmin: { condition: (_data, siblingData) => Boolean(siblingData?.showTitle) },\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'intro',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.displayIntro),\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'title', type: 'text', required: true, label: labelForKey(keys.fieldTitle) },\n\t\t// Unnamed tabs are presentational only: fields/flow/actions/response/display stay at the\n\t\t// document root. Fields keeps default focus (authors spend their time there and showTitle\n\t\t// defaults off); Display follows as the next visitor-facing concern.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField] },\n\t\t\t\t{ label: labelForKey(keys.tabDisplay), fields: [displayField] },\n\t\t\t\t{ label: labelForKey(keys.tabFlow), fields: [flowField] },\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t\t{ label: labelForKey(keys.tabResponse), fields: [responseField] },\n\t\t\t],\n\t\t},\n\t\t// Poll lifecycle config; identifiers and behavior, never localized. Sidebar keeps parity with\n\t\t// the pre-group showResults/resultsField placement. `resultsVisibility` is defaulted and not\n\t\t// clearable rather than `required` for the same generated-types reason as `response.type`.\n\t\t{\n\t\t\tname: 'poll',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollGroup),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: 'enabled',\n\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: labelForKey(keys.pollEnabled),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'resultsField',\n\t\t\t\t\ttype: 'text',\n\t\t\t\t\tlabel: labelForKey(keys.pollResultsField),\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tdescription: labelForKey(keys.pollResultsFieldDescription),\n\t\t\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.enabled),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'resultsVisibility',\n\t\t\t\t\ttype: 'select',\n\t\t\t\t\tdefaultValue: 'afterVote',\n\t\t\t\t\tlabel: labelForKey(keys.pollResultsVisibility),\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tisClearable: false,\n\t\t\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.enabled),\n\t\t\t\t\t},\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterVote), value: 'afterVote' },\n\t\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterClose), value: 'afterClose' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'closesAt',\n\t\t\t\t\ttype: 'date',\n\t\t\t\t\tlabel: labelForKey(keys.pollClosesAt),\n\t\t\t\t\tadmin: {\n\t\t\t\t\t\tdate: { pickerAppearance: 'dayAndTime' },\n\t\t\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.enabled),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t...buildPollOptionSourceFields(pollSourceRegistry ?? new Map()),\n\t\t\t\t// The outcome is written exclusively by `resolvePollOutcome` (host domain logic, server-side\n\t\t\t\t// with overrideAccess). Field-level create/update access blocks every non-override write:\n\t\t\t\t// Payload silently drops the denied value rather than erroring, so admin saves and API\n\t\t\t\t// updates can never set or clear a winner. `admin.readOnly` mirrors that in the UI.\n\t\t\t\t{\n\t\t\t\t\tname: 'outcome',\n\t\t\t\t\ttype: 'group',\n\t\t\t\t\tlabel: labelForKey(keys.pollOutcome),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => Boolean(siblingData?.enabled) },\n\t\t\t\t\tfields: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: 'winningValue',\n\t\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\t\tlabel: labelForKey(keys.pollWinningValue),\n\t\t\t\t\t\t\tadmin: { readOnly: true },\n\t\t\t\t\t\t\taccess: { create: () => false, update: () => false },\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: 'resolvedAt',\n\t\t\t\t\t\t\ttype: 'date',\n\t\t\t\t\t\t\tlabel: labelForKey(keys.pollResolvedAt),\n\t\t\t\t\t\t\tadmin: { readOnly: true },\n\t\t\t\t\t\t\taccess: { create: () => false, update: () => false },\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t\taccess: resultsAccess,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: { group: 'Forms', useAsTitle: 'title', ...(overrides?.admin ?? {}) },\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -2,10 +2,17 @@ import { defaultConditionType } from "./fieldTypes.js";
|
|
|
2
2
|
//#region src/conditions/conditionType.ts
|
|
3
3
|
/** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */
|
|
4
4
|
const conditionTypeForDefinition = (definition) => definition.conditionType ?? defaultConditionType(definition.value);
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* A serializable slug -> condition type map for every conditionable field type, handed to the client
|
|
7
|
+
* builder. Valueless ('none' kind, display-only) types are left out so they never appear as operands;
|
|
8
|
+
* consumers treat a missing entry as "not conditionable". An explicit `conditionType` opts back in.
|
|
9
|
+
*/
|
|
6
10
|
const buildConditionTypeMap = (registry) => {
|
|
7
11
|
const map = {};
|
|
8
|
-
for (const definition of registry.values())
|
|
12
|
+
for (const definition of registry.values()) {
|
|
13
|
+
if (definition.value === "none" && definition.conditionType == null) continue;
|
|
14
|
+
map[definition.type] = conditionTypeForDefinition(definition);
|
|
15
|
+
}
|
|
9
16
|
return map;
|
|
10
17
|
};
|
|
11
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditionType.js","names":[],"sources":["../../src/conditions/conditionType.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../fields/types'\nimport { type ConditionFieldType, defaultConditionType } from './fieldTypes'\n\n/** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */\nexport const conditionTypeForDefinition = (\n\tdefinition: Pick<AnyFormFieldDefinition, 'value' | 'conditionType'>\n): ConditionFieldType => definition.conditionType ?? defaultConditionType(definition.value)\n\n
|
|
1
|
+
{"version":3,"file":"conditionType.js","names":[],"sources":["../../src/conditions/conditionType.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../fields/types'\nimport { type ConditionFieldType, defaultConditionType } from './fieldTypes'\n\n/** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */\nexport const conditionTypeForDefinition = (\n\tdefinition: Pick<AnyFormFieldDefinition, 'value' | 'conditionType'>\n): ConditionFieldType => definition.conditionType ?? defaultConditionType(definition.value)\n\n/**\n * A serializable slug -> condition type map for every conditionable field type, handed to the client\n * builder. Valueless ('none' kind, display-only) types are left out so they never appear as operands;\n * consumers treat a missing entry as \"not conditionable\". An explicit `conditionType` opts back in.\n */\nexport const buildConditionTypeMap = (\n\tregistry: Map<string, AnyFormFieldDefinition>\n): Record<string, ConditionFieldType> => {\n\tconst map: Record<string, ConditionFieldType> = {}\n\tfor (const definition of registry.values()) {\n\t\tif (definition.value === 'none' && definition.conditionType == null) {\n\t\t\tcontinue\n\t\t}\n\t\tmap[definition.type] = conditionTypeForDefinition(definition)\n\t}\n\treturn map\n}\n"],"mappings":";;;AAIA,MAAa,8BACZ,eACwB,WAAW,iBAAiB,qBAAqB,WAAW,KAAK;;;;;;AAO1F,MAAa,yBACZ,aACwC;CACxC,MAAM,MAA0C,CAAC;CACjD,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,WAAW,UAAU,UAAU,WAAW,iBAAiB,MAC9D;EAED,IAAI,WAAW,QAAQ,2BAA2B,UAAU;CAC7D;CACA,OAAO;AACR"}
|
|
@@ -29,7 +29,8 @@ const normalizeFormConditions = (fields, conditionTypes) => {
|
|
|
29
29
|
const operandTypes = /* @__PURE__ */ new Map();
|
|
30
30
|
for (const row of fields) {
|
|
31
31
|
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
32
|
-
|
|
32
|
+
const conditionType = conditionTypes[row.blockType];
|
|
33
|
+
if (name.length > 0 && conditionType) operandTypes.set(name, conditionType);
|
|
33
34
|
}
|
|
34
35
|
return fields.map((row) => {
|
|
35
36
|
const visibleWhen = normalizeOne(row.visibleWhen, operandTypes);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalizeConditions.js","names":[],"sources":["../../src/conditions/normalizeConditions.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport { transformWhereQuery } from 'payload/shared'\nimport type { ConditionFieldType } from './fieldTypes'\nimport { conditionOperators } from './fieldTypes'\n\nexport type FieldRow = {\n\tblockType: string\n\tname?: string\n\tvisibleWhen?: unknown\n\tvalidateWhen?: unknown\n\t[key: string]: unknown\n}\n\nconst isValidConstraint = (\n\tconstraint: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): boolean => {\n\tif (constraint == null || typeof constraint !== 'object') {\n\t\treturn false\n\t}\n\tconst field = Object.keys(constraint as object)[0]\n\tif (!field) {\n\t\treturn false\n\t}\n\tconst type = operandTypes.get(field)\n\tif (!type) {\n\t\treturn false\n\t}\n\tconst ops = (constraint as Record<string, unknown>)[field]\n\tif (ops == null || typeof ops !== 'object') {\n\t\treturn false\n\t}\n\tconst operator = Object.keys(ops as object)[0]\n\treturn Boolean(operator && (conditionOperators[type] as readonly string[]).includes(operator))\n}\n\nconst normalizeOne = (\n\traw: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): Where | undefined => {\n\tif (raw == null || typeof raw !== 'object' || Object.keys(raw as object).length === 0) {\n\t\treturn undefined\n\t}\n\tconst canonical = transformWhereQuery(raw as Where)\n\tconst or = Array.isArray(canonical.or)\n\t\t? canonical.or\n\t\t: Array.isArray(canonical.and)\n\t\t\t? [{ and: canonical.and }]\n\t\t\t: []\n\tconst groups = or\n\t\t.map((group) => {\n\t\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as unknown[]) : []\n\t\t\treturn and.filter((constraint) => isValidConstraint(constraint, operandTypes))\n\t\t})\n\t\t.filter((group) => group.length > 0)\n\treturn groups.length > 0 ? ({ or: groups.map((and) => ({ and })) } as Where) : undefined\n}\n\n/**\n * Normalize every field's `visibleWhen`/`validateWhen` against the form's own field list. Canonicalizes\n * to OR-of-ANDs and strips constraints whose operand field is missing or whose operator is invalid for\n * that field's condition type, so stored conditions always match what `evaluateCondition` can run.\n */\nexport const normalizeFormConditions = (\n\tfields: FieldRow[],\n\tconditionTypes: Record<string, ConditionFieldType>\n): FieldRow[] => {\n\tconst operandTypes = new Map<string, ConditionFieldType>()\n\tfor (const row of fields) {\n\t\tconst name = typeof row.name === 'string' ? row.name.trim() : ''\n\t\tif (name.length > 0) {\n\t\t\toperandTypes.set(name,
|
|
1
|
+
{"version":3,"file":"normalizeConditions.js","names":[],"sources":["../../src/conditions/normalizeConditions.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport { transformWhereQuery } from 'payload/shared'\nimport type { ConditionFieldType } from './fieldTypes'\nimport { conditionOperators } from './fieldTypes'\n\nexport type FieldRow = {\n\tblockType: string\n\tname?: string\n\tvisibleWhen?: unknown\n\tvalidateWhen?: unknown\n\t[key: string]: unknown\n}\n\nconst isValidConstraint = (\n\tconstraint: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): boolean => {\n\tif (constraint == null || typeof constraint !== 'object') {\n\t\treturn false\n\t}\n\tconst field = Object.keys(constraint as object)[0]\n\tif (!field) {\n\t\treturn false\n\t}\n\tconst type = operandTypes.get(field)\n\tif (!type) {\n\t\treturn false\n\t}\n\tconst ops = (constraint as Record<string, unknown>)[field]\n\tif (ops == null || typeof ops !== 'object') {\n\t\treturn false\n\t}\n\tconst operator = Object.keys(ops as object)[0]\n\treturn Boolean(operator && (conditionOperators[type] as readonly string[]).includes(operator))\n}\n\nconst normalizeOne = (\n\traw: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): Where | undefined => {\n\tif (raw == null || typeof raw !== 'object' || Object.keys(raw as object).length === 0) {\n\t\treturn undefined\n\t}\n\tconst canonical = transformWhereQuery(raw as Where)\n\tconst or = Array.isArray(canonical.or)\n\t\t? canonical.or\n\t\t: Array.isArray(canonical.and)\n\t\t\t? [{ and: canonical.and }]\n\t\t\t: []\n\tconst groups = or\n\t\t.map((group) => {\n\t\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as unknown[]) : []\n\t\t\treturn and.filter((constraint) => isValidConstraint(constraint, operandTypes))\n\t\t})\n\t\t.filter((group) => group.length > 0)\n\treturn groups.length > 0 ? ({ or: groups.map((and) => ({ and })) } as Where) : undefined\n}\n\n/**\n * Normalize every field's `visibleWhen`/`validateWhen` against the form's own field list. Canonicalizes\n * to OR-of-ANDs and strips constraints whose operand field is missing or whose operator is invalid for\n * that field's condition type, so stored conditions always match what `evaluateCondition` can run.\n */\nexport const normalizeFormConditions = (\n\tfields: FieldRow[],\n\tconditionTypes: Record<string, ConditionFieldType>\n): FieldRow[] => {\n\tconst operandTypes = new Map<string, ConditionFieldType>()\n\tfor (const row of fields) {\n\t\tconst name = typeof row.name === 'string' ? row.name.trim() : ''\n\t\t// A type absent from the map is not conditionable (display-only, e.g. message): constraints\n\t\t// referencing it are stripped, matching the client operand list.\n\t\tconst conditionType = conditionTypes[row.blockType]\n\t\tif (name.length > 0 && conditionType) {\n\t\t\toperandTypes.set(name, conditionType)\n\t\t}\n\t}\n\treturn fields.map((row) => {\n\t\tconst visibleWhen = normalizeOne(row.visibleWhen, operandTypes)\n\t\tconst validateWhen = normalizeOne(row.validateWhen, operandTypes)\n\t\treturn { ...row, visibleWhen, validateWhen }\n\t})\n}\n"],"mappings":";;;AAaA,MAAM,qBACL,YACA,iBACa;CACb,IAAI,cAAc,QAAQ,OAAO,eAAe,UAC/C,OAAO;CAER,MAAM,QAAQ,OAAO,KAAK,UAAoB,EAAE;CAChD,IAAI,CAAC,OACJ,OAAO;CAER,MAAM,OAAO,aAAa,IAAI,KAAK;CACnC,IAAI,CAAC,MACJ,OAAO;CAER,MAAM,MAAO,WAAuC;CACpD,IAAI,OAAO,QAAQ,OAAO,QAAQ,UACjC,OAAO;CAER,MAAM,WAAW,OAAO,KAAK,GAAa,EAAE;CAC5C,OAAO,QAAQ,YAAa,mBAAmB,MAA4B,SAAS,QAAQ,CAAC;AAC9F;AAEA,MAAM,gBACL,KACA,iBACuB;CACvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAa,EAAE,WAAW,GACnF;CAED,MAAM,YAAY,oBAAoB,GAAY;CAMlD,MAAM,UALK,MAAM,QAAQ,UAAU,EAAE,IAClC,UAAU,KACV,MAAM,QAAQ,UAAU,GAAG,IAC1B,CAAC,EAAE,KAAK,UAAU,IAAI,CAAC,IACvB,CAAC,GAEH,KAAK,UAAU;EAEf,QADY,MAAM,QAAS,MAAgB,GAAG,IAAM,MAAgB,MAAoB,CAAC,GAC9E,QAAQ,eAAe,kBAAkB,YAAY,YAAY,CAAC;CAC9E,CAAC,EACA,QAAQ,UAAU,MAAM,SAAS,CAAC;CACpC,OAAO,OAAO,SAAS,IAAK,EAAE,IAAI,OAAO,KAAK,SAAS,EAAE,IAAI,EAAE,EAAE,IAAc,KAAA;AAChF;;;;;;AAOA,MAAa,2BACZ,QACA,mBACgB;CAChB,MAAM,+BAAe,IAAI,IAAgC;CACzD,KAAK,MAAM,OAAO,QAAQ;EACzB,MAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,IAAI;EAG9D,MAAM,gBAAgB,eAAe,IAAI;EACzC,IAAI,KAAK,SAAS,KAAK,eACtB,aAAa,IAAI,MAAM,aAAa;CAEtC;CACA,OAAO,OAAO,KAAK,QAAQ;EAC1B,MAAM,cAAc,aAAa,IAAI,aAAa,YAAY;EAC9D,MAAM,eAAe,aAAa,IAAI,cAAc,YAAY;EAChE,OAAO;GAAE,GAAG;GAAK;GAAa;EAAa;CAC5C,CAAC;AACF"}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { AnyConsentSource } from "../defineConsentSource.js";
|
|
2
2
|
|
|
3
3
|
//#region src/consent/builtin/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Built-in consent sources with content-bearing config fields (the static source's link label)
|
|
6
|
+
* carrying `localized: true` when `localize` is true. `pageReference` config holds only
|
|
7
|
+
* identifiers and is shared static.
|
|
8
|
+
*/
|
|
9
|
+
declare const buildDefaultConsentSources: (localize: boolean) => Record<string, AnyConsentSource>;
|
|
10
|
+
/**
|
|
11
|
+
* Prebuilt with content localization on; with `localizeContent: false`, derive overrides from
|
|
12
|
+
* `buildDefaultConsentSources(false)` instead so a spread does not reintroduce `localized`.
|
|
13
|
+
*/
|
|
4
14
|
declare const defaultConsentSources: Record<string, AnyConsentSource>;
|
|
5
15
|
//#endregion
|
|
6
|
-
export { defaultConsentSources };
|
|
16
|
+
export { buildDefaultConsentSources, defaultConsentSources };
|
|
7
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { pageReferenceSource } from "./pageReference.js";
|
|
2
|
-
import {
|
|
2
|
+
import { buildStaticSource } from "./static.js";
|
|
3
3
|
//#region src/consent/builtin/index.ts
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Built-in consent sources with content-bearing config fields (the static source's link label)
|
|
6
|
+
* carrying `localized: true` when `localize` is true. `pageReference` config holds only
|
|
7
|
+
* identifiers and is shared static.
|
|
8
|
+
*/
|
|
9
|
+
const buildDefaultConsentSources = (localize) => ({
|
|
10
|
+
static: buildStaticSource(localize),
|
|
6
11
|
pageReference: pageReferenceSource
|
|
7
|
-
};
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Prebuilt with content localization on; with `localizeContent: false`, derive overrides from
|
|
15
|
+
* `buildDefaultConsentSources(false)` instead so a spread does not reintroduce `localized`.
|
|
16
|
+
*/
|
|
17
|
+
const defaultConsentSources = buildDefaultConsentSources(true);
|
|
8
18
|
//#endregion
|
|
9
|
-
export { defaultConsentSources };
|
|
19
|
+
export { buildDefaultConsentSources, defaultConsentSources };
|
|
10
20
|
|
|
11
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/consent/builtin/index.ts"],"sourcesContent":["import type { AnyConsentSource } from '../defineConsentSource'\nimport { pageReferenceSource } from './pageReference'\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/consent/builtin/index.ts"],"sourcesContent":["import type { AnyConsentSource } from '../defineConsentSource'\nimport { pageReferenceSource } from './pageReference'\nimport { buildStaticSource } from './static'\n\n/**\n * Built-in consent sources with content-bearing config fields (the static source's link label)\n * carrying `localized: true` when `localize` is true. `pageReference` config holds only\n * identifiers and is shared static.\n */\nexport const buildDefaultConsentSources = (\n\tlocalize: boolean\n): Record<string, AnyConsentSource> => ({\n\tstatic: buildStaticSource(localize) as AnyConsentSource,\n\tpageReference: pageReferenceSource as AnyConsentSource,\n})\n\n/**\n * Prebuilt with content localization on; with `localizeContent: false`, derive overrides from\n * `buildDefaultConsentSources(false)` instead so a spread does not reintroduce `localized`.\n */\nexport const defaultConsentSources: Record<string, AnyConsentSource> =\n\tbuildDefaultConsentSources(true)\n"],"mappings":";;;;;;;;AASA,MAAa,8BACZ,cACuC;CACvC,QAAQ,kBAAkB,QAAQ;CAClC,eAAe;AAChB;;;;;AAMA,MAAa,wBACZ,2BAA2B,IAAI"}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { localizedIf } from "../../fields/localizedIf.js";
|
|
1
2
|
import { keys } from "../../translations/keys.js";
|
|
2
3
|
import { labelFor } from "../../translations/server.js";
|
|
3
4
|
import { defineConsentSource } from "../defineConsentSource.js";
|
|
4
5
|
//#region src/consent/builtin/static.ts
|
|
5
|
-
|
|
6
|
+
/** The link `label` is visitor-facing text and follows `localize`; `url` and `version` are identifiers and never do. */
|
|
7
|
+
const buildStaticSource = (localize) => defineConsentSource({
|
|
6
8
|
type: "static",
|
|
7
9
|
label: keys.consentSourceStatic,
|
|
8
10
|
config: [
|
|
9
11
|
{
|
|
10
12
|
name: "label",
|
|
11
13
|
type: "text",
|
|
12
|
-
label: labelFor(keys.consentConfigLabel)
|
|
14
|
+
label: labelFor(keys.consentConfigLabel),
|
|
15
|
+
...localizedIf(localize)
|
|
13
16
|
},
|
|
14
17
|
{
|
|
15
18
|
name: "url",
|
|
@@ -38,7 +41,8 @@ const staticSource = defineConsentSource({
|
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
});
|
|
44
|
+
buildStaticSource(true);
|
|
41
45
|
//#endregion
|
|
42
|
-
export {
|
|
46
|
+
export { buildStaticSource };
|
|
43
47
|
|
|
44
48
|
//# sourceMappingURL=static.js.map
|