@10x-media/form-builder 0.1.0-beta.4 → 0.1.0-beta.6
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 +112 -0
- package/README.md +3 -3
- package/dist/actions/body/serializeBody.d.ts +8 -5
- package/dist/actions/body/serializeBody.js.map +1 -1
- package/dist/actions/body/textOfBody.d.ts +13 -0
- package/dist/actions/body/textOfBody.js +35 -0
- package/dist/actions/body/textOfBody.js.map +1 -0
- package/dist/actions/builtin/confirmation.js +81 -48
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +67 -38
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +9 -3
- package/dist/actions/builtin/index.js +8 -5
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +1 -1
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +72 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/fromAddresses.d.ts +21 -0
- package/dist/actions/fromAddresses.js +74 -0
- package/dist/actions/fromAddresses.js.map +1 -0
- package/dist/actions/task.js +9 -8
- package/dist/actions/task.js.map +1 -1
- package/dist/aggregation/aggregateResponses.d.ts +9 -0
- package/dist/aggregation/aggregateResponses.js +8 -14
- package/dist/aggregation/aggregateResponses.js.map +1 -1
- package/dist/aggregation/resolveResultsRequest.d.ts +19 -6
- package/dist/aggregation/resolveResultsRequest.js +58 -12
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/computeCalcFields.js +2 -1
- package/dist/calc/computeCalcFields.js.map +1 -1
- package/dist/client/ByteSizeField.d.ts +8 -0
- package/dist/client/ByteSizeField.js +3 -1
- package/dist/client/ByteSizeField.js.map +1 -1
- package/dist/client/ClosePollButton.d.ts +15 -0
- package/dist/client/ClosePollButton.js +80 -0
- package/dist/client/ClosePollButton.js.map +1 -0
- package/dist/client/CondensedArray.d.ts +15 -0
- package/dist/client/CondensedArray.js +97 -0
- package/dist/client/CondensedArray.js.map +1 -0
- package/dist/client/ConsentBlockLabel.d.ts +15 -0
- package/dist/client/ConsentBlockLabel.js +71 -0
- package/dist/client/ConsentBlockLabel.js.map +1 -0
- package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
- package/dist/client/ConsentSourceRowLabel.js +21 -0
- package/dist/client/ConsentSourceRowLabel.js.map +1 -0
- package/dist/client/EndpointOptionsSelect.d.ts +46 -0
- package/dist/client/EndpointOptionsSelect.js +106 -0
- package/dist/client/EndpointOptionsSelect.js.map +1 -0
- package/dist/client/FieldBlockLabel.d.ts +18 -0
- package/dist/client/FieldBlockLabel.js +34 -0
- package/dist/client/FieldBlockLabel.js.map +1 -0
- package/dist/client/FieldNameSelect.d.ts +21 -4
- package/dist/client/FieldNameSelect.js +29 -15
- package/dist/client/FieldNameSelect.js.map +1 -1
- package/dist/client/FlowBuilder.d.ts +2 -1
- package/dist/client/FlowBuilder.js +63 -57
- package/dist/client/FlowBuilder.js.map +1 -1
- package/dist/client/RecipientsSelect.d.ts +31 -0
- package/dist/client/RecipientsSelect.js +174 -0
- package/dist/client/RecipientsSelect.js.map +1 -0
- package/dist/client/RuleDescription.d.ts +21 -0
- package/dist/client/RuleDescription.js +19 -0
- package/dist/client/RuleDescription.js.map +1 -0
- package/dist/client/admin.css +41 -0
- package/dist/client/endpointOptions.js +29 -0
- package/dist/client/endpointOptions.js.map +1 -0
- package/dist/client/flowAuthoring.js +114 -7
- package/dist/client/flowAuthoring.js.map +1 -1
- package/dist/client/synthesizeClientField.js +15 -8
- package/dist/client/synthesizeClientField.js.map +1 -1
- package/dist/collections/buttonFields.d.ts +34 -0
- package/dist/collections/buttonFields.js +35 -0
- package/dist/collections/buttonFields.js.map +1 -0
- package/dist/collections/cells/FieldCountCell.d.ts +15 -0
- package/dist/collections/cells/FieldCountCell.js +18 -0
- package/dist/collections/cells/FieldCountCell.js.map +1 -0
- package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
- package/dist/collections/cells/FlowStepsCell.js +17 -0
- package/dist/collections/cells/FlowStepsCell.js.map +1 -0
- package/dist/collections/formSubmissions.js +23 -17
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +323 -134
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/redirectFields.d.ts +29 -0
- package/dist/consent/applyConsentStatements.d.ts +14 -0
- package/dist/consent/applyConsentStatements.js +17 -0
- package/dist/consent/applyConsentStatements.js.map +1 -0
- package/dist/consent/captureConsent.d.ts +31 -11
- package/dist/consent/captureConsent.js +53 -13
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +62 -0
- package/dist/consent/consentSourcesField.js +86 -0
- package/dist/consent/consentSourcesField.js.map +1 -0
- package/dist/consent/resolveConsentEntries.d.ts +24 -0
- package/dist/consent/resolveConsentEntries.js +43 -0
- package/dist/consent/resolveConsentEntries.js.map +1 -0
- package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
- package/dist/consent/resolveConsentSourcesRequest.js +55 -0
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
- package/dist/consent/resolveConsentStatements.d.ts +45 -0
- package/dist/consent/resolveConsentStatements.js +44 -0
- package/dist/consent/resolveConsentStatements.js.map +1 -0
- package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
- package/dist/consent/resolvePublishedVersionRef.js +13 -10
- package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
- package/dist/consent/types.d.ts +53 -0
- package/dist/email/departments.d.ts +59 -0
- package/dist/email/departments.js +87 -0
- package/dist/email/departments.js.map +1 -0
- package/dist/email/departmentsField.d.ts +53 -0
- package/dist/email/departmentsField.js +73 -0
- package/dist/email/departmentsField.js.map +1 -0
- package/dist/exports/client.d.ts +11 -1
- package/dist/exports/client.js +11 -1
- package/dist/exports/react.d.ts +8 -3
- package/dist/exports/react.js +6 -2
- package/dist/exports/rsc.d.ts +3 -1
- package/dist/exports/rsc.js +4 -2
- package/dist/exports/types.d.ts +16 -8
- package/dist/fields/buildFieldBlocks.js +28 -13
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.js +7 -0
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/checkbox.js +8 -0
- package/dist/fields/builtin/checkbox.js.map +1 -1
- package/dist/fields/builtin/consent.js +32 -21
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/country.d.ts +13 -0
- package/dist/fields/builtin/country.js +30 -0
- package/dist/fields/builtin/country.js.map +1 -0
- package/dist/fields/builtin/date.js +2 -0
- package/dist/fields/builtin/date.js.map +1 -1
- package/dist/fields/builtin/file.js +14 -7
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +8 -6
- package/dist/fields/builtin/index.js +12 -11
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +8 -4
- package/dist/fields/builtin/message.js.map +1 -1
- package/dist/fields/builtin/repeater.js +1 -0
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +1 -0
- package/dist/fields/builtin/select.js.map +1 -1
- package/dist/fields/builtin/state.d.ts +13 -0
- package/dist/fields/builtin/state.js +30 -0
- package/dist/fields/builtin/state.js.map +1 -0
- package/dist/fields/data/regions.d.ts +21 -0
- package/dist/fields/data/regions.js +1238 -0
- package/dist/fields/data/regions.js.map +1 -0
- package/dist/fields/fieldKey.d.ts +15 -0
- package/dist/fields/fieldKey.js +16 -0
- package/dist/fields/fieldKey.js.map +1 -0
- package/dist/fields/fieldNamesOfType.js +13 -8
- package/dist/fields/fieldNamesOfType.js.map +1 -1
- package/dist/fields/instanceOptions.js +20 -0
- package/dist/fields/instanceOptions.js.map +1 -0
- package/dist/fields/registry.d.ts +1 -0
- package/dist/fields/registry.js +21 -2
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +46 -9
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +63 -5
- package/dist/flow/engine.d.ts +6 -4
- package/dist/flow/engine.js +11 -6
- package/dist/flow/engine.js.map +1 -1
- package/dist/flow/normalizeFlow.js +16 -7
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/flow/types.d.ts +12 -3
- package/dist/flow/types.js +11 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/form/pollState.d.ts +4 -4
- package/dist/form/pollState.js.map +1 -1
- package/dist/form/toFormDocument.d.ts +54 -12
- package/dist/form/toFormDocument.js +56 -8
- package/dist/form/toFormDocument.js.map +1 -1
- package/dist/form/types.d.ts +31 -14
- package/dist/index.d.ts +153 -29
- package/dist/index.js +47 -15
- package/dist/index.js.map +1 -1
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +24 -13
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +5 -0
- package/dist/plugin/uploadsCollection.js +10 -1
- package/dist/plugin/uploadsCollection.js.map +1 -1
- package/dist/poll/applyPollOptions.js +3 -2
- package/dist/poll/applyPollOptions.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -5
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- package/dist/poll/closeJob.d.ts +52 -0
- package/dist/poll/closeJob.js +119 -0
- package/dist/poll/closeJob.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +10 -1
- package/dist/poll/definePollOptionSource.js.map +1 -1
- package/dist/poll/definePollType.d.ts +34 -0
- package/dist/poll/definePollType.js +6 -0
- package/dist/poll/definePollType.js.map +1 -0
- package/dist/poll/effectivePollOptions.d.ts +38 -0
- package/dist/poll/effectivePollOptions.js +64 -0
- package/dist/poll/effectivePollOptions.js.map +1 -0
- package/dist/poll/mostVoted.d.ts +21 -0
- package/dist/poll/mostVoted.js +32 -0
- package/dist/poll/mostVoted.js.map +1 -0
- package/dist/poll/outcomeBeforeChange.js +91 -0
- package/dist/poll/outcomeBeforeChange.js.map +1 -0
- package/dist/poll/outcomeFields.d.ts +38 -0
- package/dist/poll/outcomeFields.js +52 -0
- package/dist/poll/outcomeFields.js.map +1 -0
- package/dist/poll/pollTypeRegistry.d.ts +34 -0
- package/dist/poll/pollTypeRegistry.js +71 -0
- package/dist/poll/pollTypeRegistry.js.map +1 -0
- package/dist/poll/resolvePollCloseRequest.js +105 -0
- package/dist/poll/resolvePollCloseRequest.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +2 -1
- package/dist/poll/resolvePollOptions.js +5 -17
- package/dist/poll/resolvePollOptions.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +51 -0
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +22 -9
- package/dist/poll/resolvePollOutcome.js +54 -14
- package/dist/poll/resolvePollOutcome.js.map +1 -1
- package/dist/poll/resultsField.js +21 -0
- package/dist/poll/resultsField.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +2 -1
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +8 -8
- package/dist/react/Form.js +74 -62
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +6 -4
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +3 -3
- package/dist/react/FormControls.js +3 -3
- package/dist/react/FormControls.js.map +1 -1
- package/dist/react/FormResults.d.ts +5 -5
- package/dist/react/FormResults.js +6 -6
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/Poll.d.ts +2 -2
- package/dist/react/Poll.js +5 -5
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/contract.d.ts +2 -3
- package/dist/react/contract.js.map +1 -1
- package/dist/react/primitives/Checkbox.d.ts +4 -2
- package/dist/react/primitives/Checkbox.js +2 -1
- package/dist/react/primitives/Checkbox.js.map +1 -1
- package/dist/react/primitives/FieldShell.d.ts +2 -4
- package/dist/react/primitives/FieldShell.js +12 -19
- package/dist/react/primitives/FieldShell.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -2
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -2
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +78 -34
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +1 -3
- package/dist/react/renderers/date.js.map +1 -1
- package/dist/react/renderers/email.js +1 -2
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +1 -2
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +3 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/number.js +1 -2
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +45 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +7 -10
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -2
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -2
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -2
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/react/state.d.ts +0 -3
- package/dist/react/state.js +7 -11
- package/dist/react/state.js.map +1 -1
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +0 -1
- package/dist/react/useField.js.map +1 -1
- package/dist/react/useFormState.d.ts +1 -1
- package/dist/react/useFormState.js +1 -1
- package/dist/react/useFormState.js.map +1 -1
- package/dist/react/validateField.js +3 -6
- package/dist/react/validateField.js.map +1 -1
- package/dist/recall/interpolate.d.ts +4 -3
- package/dist/recall/interpolate.js +5 -4
- package/dist/recall/interpolate.js.map +1 -1
- package/dist/recall/resolver.d.ts +1 -1
- package/dist/recall/resolver.js +14 -2
- package/dist/recall/resolver.js.map +1 -1
- package/dist/spam/captcha.d.ts +1 -1
- package/dist/spam/captcha.js +1 -1
- package/dist/spam/captcha.js.map +1 -1
- package/dist/submissions/SubmissionAnswers.js +6 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +100 -64
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- package/dist/submissions/createSubmission.d.ts +36 -0
- package/dist/submissions/createSubmission.js +26 -0
- package/dist/submissions/createSubmission.js.map +1 -0
- package/dist/submissions/runSubmission.d.ts +10 -0
- package/dist/submissions/runSubmission.js +31 -19
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +11 -4
- package/dist/submissions/validateSubmission.js +39 -23
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/votedCookie.js +3 -3
- package/dist/submissions/votedCookie.js.map +1 -1
- package/dist/translations/de.js +104 -26
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +105 -27
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +99 -21
- package/dist/translations/keys.js +100 -22
- package/dist/translations/keys.js.map +1 -1
- package/dist/uploads/resolveFileRef.js +2 -1
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/validation/buildRuleBlocks.js +18 -17
- package/dist/validation/buildRuleBlocks.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -0
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/matchesField.js +3 -7
- package/dist/validation/builtin/matchesField.js.map +1 -1
- package/dist/validation/builtin/max.js +1 -0
- package/dist/validation/builtin/max.js.map +1 -1
- package/dist/validation/builtin/maxDate.js +1 -0
- package/dist/validation/builtin/maxDate.js.map +1 -1
- package/dist/validation/builtin/maxLength.js +1 -0
- package/dist/validation/builtin/maxLength.js.map +1 -1
- package/dist/validation/builtin/min.js +1 -0
- package/dist/validation/builtin/min.js.map +1 -1
- package/dist/validation/builtin/minDate.js +1 -0
- package/dist/validation/builtin/minDate.js.map +1 -1
- package/dist/validation/builtin/minLength.js +1 -0
- package/dist/validation/builtin/minLength.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/builtin/oneOf.js +1 -0
- package/dist/validation/builtin/oneOf.js.map +1 -1
- package/dist/validation/builtin/pattern.js +11 -2
- package/dist/validation/builtin/pattern.js.map +1 -1
- package/dist/validation/builtin/url.js +1 -0
- package/dist/validation/builtin/url.js.map +1 -1
- package/dist/validation/builtin/validateRegexParams.js +44 -0
- package/dist/validation/builtin/validateRegexParams.js.map +1 -0
- package/dist/validation/fieldTargetParam.d.ts +22 -0
- package/dist/validation/fieldTargetParam.js +38 -0
- package/dist/validation/fieldTargetParam.js.map +1 -0
- package/dist/validation/runValidation.js +8 -29
- package/dist/validation/runValidation.js.map +1 -1
- package/dist/validation/types.d.ts +5 -11
- package/package.json +6 -6
- package/dist/consent/buildConsentSourceConfig.js +0 -49
- package/dist/consent/buildConsentSourceConfig.js.map +0 -1
- package/dist/consent/builtin/index.d.ts +0 -17
- package/dist/consent/builtin/index.js +0 -21
- package/dist/consent/builtin/index.js.map +0 -1
- package/dist/consent/builtin/pageReference.js +0 -75
- package/dist/consent/builtin/pageReference.js.map +0 -1
- package/dist/consent/builtin/static.js +0 -48
- package/dist/consent/builtin/static.js.map +0 -1
- package/dist/consent/defineConsentSource.d.ts +0 -35
- package/dist/consent/defineConsentSource.js +0 -6
- package/dist/consent/defineConsentSource.js.map +0 -1
- package/dist/consent/registry.d.ts +0 -16
- package/dist/consent/registry.js +0 -16
- package/dist/consent/registry.js.map +0 -1
- package/dist/consent/resolveConsentLinks.d.ts +0 -19
- package/dist/consent/resolveConsentLinks.js +0 -26
- package/dist/consent/resolveConsentLinks.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { registerPollCloseTask } from "../poll/closeJob.js";
|
|
1
2
|
import { buildFormsCollection } from "../collections/forms.js";
|
|
2
3
|
import { buildSubmissionsCollection } from "../collections/formSubmissions.js";
|
|
3
4
|
import { registerActionsTask } from "../actions/task.js";
|
|
4
5
|
import { attachUploadsCollection } from "./uploadsCollection.js";
|
|
5
6
|
//#region src/plugin/registerCollections.ts
|
|
6
|
-
const registerCollections = ({ config, registry, ruleRegistry,
|
|
7
|
+
const registerCollections = ({ config, registry, ruleRegistry, consentSources, consentSnapshot, actionRegistry, richText, hasJobsPlugin, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, buttons, response, fromAddresses, departments, redirectRelationships, overrides }) => {
|
|
7
8
|
registerActionsTask(config, actionRegistry, richText);
|
|
9
|
+
registerPollCloseTask(config);
|
|
8
10
|
const hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin;
|
|
9
11
|
const uploadSlug = uploads === false ? void 0 : uploads.collection;
|
|
10
12
|
if (uploads !== false) attachUploadsCollection({
|
|
@@ -14,21 +16,11 @@ const registerCollections = ({ config, registry, ruleRegistry, consentRegistry,
|
|
|
14
16
|
});
|
|
15
17
|
config.collections = [
|
|
16
18
|
...config.collections ?? [],
|
|
17
|
-
buildFormsCollection({
|
|
18
|
-
registry,
|
|
19
|
-
ruleRegistry,
|
|
20
|
-
consentRegistry,
|
|
21
|
-
actionRegistry,
|
|
22
|
-
localizeContent,
|
|
23
|
-
uploadsCollectionSlug: uploadSlug,
|
|
24
|
-
resultsAccess,
|
|
25
|
-
pollSourceRegistry,
|
|
26
|
-
overrides: overrides?.forms
|
|
27
|
-
}),
|
|
28
19
|
buildSubmissionsCollection({
|
|
29
20
|
registry,
|
|
30
21
|
ruleRegistry,
|
|
31
|
-
|
|
22
|
+
consentSources,
|
|
23
|
+
consentSnapshot,
|
|
32
24
|
actionRegistry,
|
|
33
25
|
richText,
|
|
34
26
|
events,
|
|
@@ -39,6 +31,25 @@ const registerCollections = ({ config, registry, ruleRegistry, consentRegistry,
|
|
|
39
31
|
pollSourceRegistry,
|
|
40
32
|
showRawFields: showSubmissionRawFields,
|
|
41
33
|
overrides: overrides?.formSubmissions
|
|
34
|
+
}),
|
|
35
|
+
buildFormsCollection({
|
|
36
|
+
registry,
|
|
37
|
+
ruleRegistry,
|
|
38
|
+
consentSources,
|
|
39
|
+
actionRegistry,
|
|
40
|
+
localizeContent,
|
|
41
|
+
richText,
|
|
42
|
+
uploadsCollectionSlug: uploadSlug,
|
|
43
|
+
resultsAccess,
|
|
44
|
+
pollSourceRegistry,
|
|
45
|
+
pollTypeRegistry,
|
|
46
|
+
outcomeFields,
|
|
47
|
+
buttons,
|
|
48
|
+
response,
|
|
49
|
+
fromAddresses,
|
|
50
|
+
departments,
|
|
51
|
+
redirectRelationships,
|
|
52
|
+
overrides: overrides?.forms
|
|
42
53
|
})
|
|
43
54
|
];
|
|
44
55
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type {
|
|
1
|
+
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentSources?: ConsentSourcesResolver\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\tbuttons?: ButtonsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tresponse,\n\tfromAddresses,\n\tdepartments,\n\tredirectRelationships,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload has no nav-sort option, so it lists collections in registration order. Register\n\t// `form-submissions` before `forms` so the \"Forms\" nav group reads alphabetically\n\t// (`form-submissions` < `forms`); ordering plugin nav groups against a host's own collections\n\t// stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t]\n}\n"],"mappings":";;;;;;AAsDA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,gBACA,iBACA,gBACA,UACA,eACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,SACA,UACA,eACA,aACA,uBACA,gBACoC;CACpC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,MAAM;CAC5B,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAOnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;EACD,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;CACF;AACD"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { Config } from "payload";
|
|
2
|
+
|
|
1
3
|
//#region src/plugin/uploadsCollection.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Uploads are bring-your-own: `false` (the default) disables file fields entirely, an object points
|
|
4
6
|
* at a host-owned upload collection (created by the app with its storage adapter of choice).
|
|
7
|
+
* `mimeTypes` optionally constrains the file field's MIME picker for hosts that enforce types by other
|
|
8
|
+
* means than the collection's own `upload.mimeTypes` (which is read automatically when this is absent).
|
|
5
9
|
*/
|
|
6
10
|
type UploadsOption = false | {
|
|
7
11
|
collection: string;
|
|
12
|
+
mimeTypes?: string[];
|
|
8
13
|
};
|
|
9
14
|
//#endregion
|
|
10
15
|
export { UploadsOption };
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { buildUploadOwnerStamp, buildUploadRateLimit } from "../spam/uploadHooks.js";
|
|
2
2
|
//#region src/plugin/uploadsCollection.ts
|
|
3
|
+
/**
|
|
4
|
+
* The MIME types the file field's picker should offer: the host upload collection's own
|
|
5
|
+
* `upload.mimeTypes`, or undefined (no constraint, so the picker keeps its full list). Read at
|
|
6
|
+
* plugin-factory time, before the field registry freezes; `attachUploadsCollection` runs too late.
|
|
7
|
+
*/
|
|
8
|
+
const readUploadCollectionMimeTypes = (config, slug) => {
|
|
9
|
+
const upload = (config.collections?.find((collection) => collection.slug === slug))?.upload;
|
|
10
|
+
return typeof upload === "object" && Array.isArray(upload.mimeTypes) ? upload.mimeTypes : void 0;
|
|
11
|
+
};
|
|
3
12
|
/** Descends presentational rows only: an `owner` field nested in a group/tabs/collapsible is not detected, so a top-level one gets appended alongside it. */
|
|
4
13
|
const hasFieldNamed = (fields, name) => fields.some((field) => {
|
|
5
14
|
if ("name" in field && field.name === name) return true;
|
|
@@ -39,6 +48,6 @@ const attachUploadsCollection = ({ config, slug, spam }) => {
|
|
|
39
48
|
config.collections = collections.map((collection) => collection.slug === slug ? attached : collection);
|
|
40
49
|
};
|
|
41
50
|
//#endregion
|
|
42
|
-
export { attachUploadsCollection };
|
|
51
|
+
export { attachUploadsCollection, readUploadCollectionMimeTypes };
|
|
43
52
|
|
|
44
53
|
//# sourceMappingURL=uploadsCollection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadsCollection.js","names":[],"sources":["../../src/plugin/uploadsCollection.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field } from 'payload'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { buildUploadOwnerStamp, buildUploadRateLimit } from '../spam/uploadHooks'\n\n/**\n * Uploads are bring-your-own: `false` (the default) disables file fields entirely, an object points\n * at a host-owned upload collection (created by the app with its storage adapter of choice).\n */\nexport type UploadsOption = false | { collection: string }\n\n/** Descends presentational rows only: an `owner` field nested in a group/tabs/collapsible is not detected, so a top-level one gets appended alongside it. */\nconst hasFieldNamed = (fields: Field[], name: string): boolean =>\n\tfields.some((field) => {\n\t\tif ('name' in field && field.name === name) {\n\t\t\treturn true\n\t\t}\n\t\treturn field.type === 'row' && hasFieldNamed(field.fields, name)\n\t})\n\ntype AttachUploadsCollectionArgs = {\n\tconfig: Config\n\tslug: string\n\tspam: ResolvedSpamConfig | false\n}\n\n/**\n * Wire the plugin's upload concerns into the host-owned upload collection named by\n * `uploads.collection`. The collection must already exist in `config.collections` and carry an\n * `upload` config; anything else is a plugin misconfiguration and throws at boot. The plugin\n * appends its hidden `owner` text field unless the host already defines a field named `owner`\n * (then that field is reused and must stay a text-compatible identity slot), and prepends the\n * spam upload hooks (rate limit, owner stamp) ahead of the host's own hooks so abuse checks run\n * first. The host entry is replaced with a new object; the host's config is never mutated.\n */\nexport const attachUploadsCollection = ({\n\tconfig,\n\tslug,\n\tspam,\n}: AttachUploadsCollectionArgs): void => {\n\tconst collections = config.collections ?? []\n\tconst target = collections.find((collection) => collection.slug === slug)\n\tif (!target) {\n\t\tthrow new Error(\n\t\t\t`@10x-media/form-builder: uploads.collection \"${slug}\" was not found in config.collections. ` +\n\t\t\t\t`Create an upload-enabled collection (with your storage adapter) and pass its slug, ` +\n\t\t\t\t`e.g. formBuilder({ uploads: { collection: 'media' } }).`\n\t\t)\n\t}\n\tif (!target.upload) {\n\t\tthrow new Error(\n\t\t\t`@10x-media/form-builder: uploads.collection \"${slug}\" has no \\`upload\\` config. ` +\n\t\t\t\t`File fields store Payload uploads, so the collection must be an upload collection ` +\n\t\t\t\t`(set \\`upload: true\\` or an upload options object on it).`\n\t\t)\n\t}\n\n\tconst fields = hasFieldNamed(target.fields, 'owner')\n\t\t? target.fields\n\t\t: [\n\t\t\t\t...target.fields,\n\t\t\t\t{ name: 'owner', type: 'text', admin: { readOnly: true, hidden: true } } as Field,\n\t\t\t]\n\n\tconst attached: CollectionConfig = {\n\t\t...target,\n\t\tfields,\n\t\t...(spam\n\t\t\t? {\n\t\t\t\t\thooks: {\n\t\t\t\t\t\t...(target.hooks ?? {}),\n\t\t\t\t\t\tbeforeOperation: [buildUploadRateLimit(spam), ...(target.hooks?.beforeOperation ?? [])],\n\t\t\t\t\t\tbeforeValidate: [buildUploadOwnerStamp(spam), ...(target.hooks?.beforeValidate ?? [])],\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t}\n\n\tconfig.collections = collections.map((collection) =>\n\t\tcollection.slug === slug ? attached : collection\n\t)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"uploadsCollection.js","names":[],"sources":["../../src/plugin/uploadsCollection.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field } from 'payload'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { buildUploadOwnerStamp, buildUploadRateLimit } from '../spam/uploadHooks'\n\n/**\n * Uploads are bring-your-own: `false` (the default) disables file fields entirely, an object points\n * at a host-owned upload collection (created by the app with its storage adapter of choice).\n * `mimeTypes` optionally constrains the file field's MIME picker for hosts that enforce types by other\n * means than the collection's own `upload.mimeTypes` (which is read automatically when this is absent).\n */\nexport type UploadsOption = false | { collection: string; mimeTypes?: string[] }\n\n/**\n * The MIME types the file field's picker should offer: the host upload collection's own\n * `upload.mimeTypes`, or undefined (no constraint, so the picker keeps its full list). Read at\n * plugin-factory time, before the field registry freezes; `attachUploadsCollection` runs too late.\n */\nexport const readUploadCollectionMimeTypes = (\n\tconfig: Config,\n\tslug: string\n): string[] | undefined => {\n\tconst target = config.collections?.find((collection) => collection.slug === slug)\n\tconst upload = target?.upload\n\treturn typeof upload === 'object' && Array.isArray(upload.mimeTypes)\n\t\t? upload.mimeTypes\n\t\t: undefined\n}\n\n/** Descends presentational rows only: an `owner` field nested in a group/tabs/collapsible is not detected, so a top-level one gets appended alongside it. */\nconst hasFieldNamed = (fields: Field[], name: string): boolean =>\n\tfields.some((field) => {\n\t\tif ('name' in field && field.name === name) {\n\t\t\treturn true\n\t\t}\n\t\treturn field.type === 'row' && hasFieldNamed(field.fields, name)\n\t})\n\ntype AttachUploadsCollectionArgs = {\n\tconfig: Config\n\tslug: string\n\tspam: ResolvedSpamConfig | false\n}\n\n/**\n * Wire the plugin's upload concerns into the host-owned upload collection named by\n * `uploads.collection`. The collection must already exist in `config.collections` and carry an\n * `upload` config; anything else is a plugin misconfiguration and throws at boot. The plugin\n * appends its hidden `owner` text field unless the host already defines a field named `owner`\n * (then that field is reused and must stay a text-compatible identity slot), and prepends the\n * spam upload hooks (rate limit, owner stamp) ahead of the host's own hooks so abuse checks run\n * first. The host entry is replaced with a new object; the host's config is never mutated.\n */\nexport const attachUploadsCollection = ({\n\tconfig,\n\tslug,\n\tspam,\n}: AttachUploadsCollectionArgs): void => {\n\tconst collections = config.collections ?? []\n\tconst target = collections.find((collection) => collection.slug === slug)\n\tif (!target) {\n\t\tthrow new Error(\n\t\t\t`@10x-media/form-builder: uploads.collection \"${slug}\" was not found in config.collections. ` +\n\t\t\t\t`Create an upload-enabled collection (with your storage adapter) and pass its slug, ` +\n\t\t\t\t`e.g. formBuilder({ uploads: { collection: 'media' } }).`\n\t\t)\n\t}\n\tif (!target.upload) {\n\t\tthrow new Error(\n\t\t\t`@10x-media/form-builder: uploads.collection \"${slug}\" has no \\`upload\\` config. ` +\n\t\t\t\t`File fields store Payload uploads, so the collection must be an upload collection ` +\n\t\t\t\t`(set \\`upload: true\\` or an upload options object on it).`\n\t\t)\n\t}\n\n\tconst fields = hasFieldNamed(target.fields, 'owner')\n\t\t? target.fields\n\t\t: [\n\t\t\t\t...target.fields,\n\t\t\t\t{ name: 'owner', type: 'text', admin: { readOnly: true, hidden: true } } as Field,\n\t\t\t]\n\n\tconst attached: CollectionConfig = {\n\t\t...target,\n\t\tfields,\n\t\t...(spam\n\t\t\t? {\n\t\t\t\t\thooks: {\n\t\t\t\t\t\t...(target.hooks ?? {}),\n\t\t\t\t\t\tbeforeOperation: [buildUploadRateLimit(spam), ...(target.hooks?.beforeOperation ?? [])],\n\t\t\t\t\t\tbeforeValidate: [buildUploadOwnerStamp(spam), ...(target.hooks?.beforeValidate ?? [])],\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t}\n\n\tconfig.collections = collections.map((collection) =>\n\t\tcollection.slug === slug ? attached : collection\n\t)\n}\n"],"mappings":";;;;;;;AAiBA,MAAa,iCACZ,QACA,SAC0B;CAE1B,MAAM,UADS,OAAO,aAAa,MAAM,eAAe,WAAW,SAAS,IAAI,IACzD;CACvB,OAAO,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,SAAS,IAChE,OAAO,YACP,KAAA;AACJ;;AAGA,MAAM,iBAAiB,QAAiB,SACvC,OAAO,MAAM,UAAU;CACtB,IAAI,UAAU,SAAS,MAAM,SAAS,MACrC,OAAO;CAER,OAAO,MAAM,SAAS,SAAS,cAAc,MAAM,QAAQ,IAAI;AAChE,CAAC;;;;;;;;;;AAiBF,MAAa,2BAA2B,EACvC,QACA,MACA,WACwC;CACxC,MAAM,cAAc,OAAO,eAAe,CAAC;CAC3C,MAAM,SAAS,YAAY,MAAM,eAAe,WAAW,SAAS,IAAI;CACxE,IAAI,CAAC,QACJ,MAAM,IAAI,MACT,gDAAgD,KAAK,kLAGtD;CAED,IAAI,CAAC,OAAO,QACX,MAAM,IAAI,MACT,gDAAgD,KAAK,wKAGtD;CAGD,MAAM,SAAS,cAAc,OAAO,QAAQ,OAAO,IAChD,OAAO,SACP,CACA,GAAG,OAAO,QACV;EAAE,MAAM;EAAS,MAAM;EAAQ,OAAO;GAAE,UAAU;GAAM,QAAQ;EAAK;CAAE,CACxE;CAEF,MAAM,WAA6B;EAClC,GAAG;EACH;EACA,GAAI,OACD,EACA,OAAO;GACN,GAAI,OAAO,SAAS,CAAC;GACrB,iBAAiB,CAAC,qBAAqB,IAAI,GAAG,GAAI,OAAO,OAAO,mBAAmB,CAAC,CAAE;GACtF,gBAAgB,CAAC,sBAAsB,IAAI,GAAG,GAAI,OAAO,OAAO,kBAAkB,CAAC,CAAE;EACtF,EACD,IACC,CAAC;CACL;CAEA,OAAO,cAAc,YAAY,KAAK,eACrC,WAAW,SAAS,OAAO,WAAW,UACvC;AACD"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
//#region src/poll/applyPollOptions.ts
|
|
2
2
|
/**
|
|
3
3
|
* Replace the options of the field instance named `resultsField` with source-resolved options,
|
|
4
|
-
* without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
5
|
-
* hand-authored options stay untouched on forms without
|
|
4
|
+
* without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
5
|
+
* (nameless bare rows never match), so hand-authored options stay untouched on forms without
|
|
6
|
+
* an option source.
|
|
6
7
|
*/
|
|
7
8
|
const applyPollOptions = (fields, resultsField, options) => {
|
|
8
9
|
if (typeof resultsField !== "string" || resultsField.length === 0) return fields;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyPollOptions.js","names":[],"sources":["../../src/poll/applyPollOptions.ts"],"sourcesContent":["import type { PollOption } from './definePollOptionSource'\n\n/**\n * Replace the options of the field instance named `resultsField` with source-resolved options,\n * without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
1
|
+
{"version":3,"file":"applyPollOptions.js","names":[],"sources":["../../src/poll/applyPollOptions.ts"],"sourcesContent":["import type { PollOption } from './definePollOptionSource'\n\n/**\n * Replace the options of the field instance named `resultsField` with source-resolved options,\n * without mutating the input. No-op when `resultsField` is unset or matches no instance\n * (nameless bare rows never match), so hand-authored options stay untouched on forms without\n * an option source.\n */\nexport const applyPollOptions = <T extends { name?: string }>(\n\tfields: T[],\n\tresultsField: string | null | undefined,\n\toptions: PollOption[]\n): T[] => {\n\tif (typeof resultsField !== 'string' || resultsField.length === 0) {\n\t\treturn fields\n\t}\n\treturn fields.map((instance) =>\n\t\tinstance.name === resultsField ? { ...instance, options } : instance\n\t)\n}\n"],"mappings":";;;;;;;AAQA,MAAa,oBACZ,QACA,cACA,YACS;CACT,IAAI,OAAO,iBAAiB,YAAY,aAAa,WAAW,GAC/D,OAAO;CAER,OAAO,OAAO,KAAK,aAClB,SAAS,SAAS,eAAe;EAAE,GAAG;EAAU;CAAQ,IAAI,QAC7D;AACD"}
|
|
@@ -27,10 +27,7 @@ const buildPollOptionSourceFields = (registry) => {
|
|
|
27
27
|
name: "optionSource",
|
|
28
28
|
type: "select",
|
|
29
29
|
label: labelForKey(keys.pollOptionSource),
|
|
30
|
-
admin: {
|
|
31
|
-
description: labelForKey(keys.pollOptionSourceDescription),
|
|
32
|
-
condition: (_data, siblingData) => Boolean(siblingData?.enabled)
|
|
33
|
-
},
|
|
30
|
+
admin: { description: labelForKey(keys.pollOptionSourceDescription) },
|
|
34
31
|
options: sources.map((source) => ({
|
|
35
32
|
label: labelFor(source.label),
|
|
36
33
|
value: source.type
|
|
@@ -39,7 +36,7 @@ const buildPollOptionSourceFields = (registry) => {
|
|
|
39
36
|
name: "sourceConfig",
|
|
40
37
|
type: "group",
|
|
41
38
|
label: labelForKey(keys.pollSourceConfig),
|
|
42
|
-
admin: { condition: (_data, siblingData) => Boolean(siblingData?.
|
|
39
|
+
admin: { condition: (_data, siblingData) => Boolean(siblingData?.optionSource) },
|
|
43
40
|
fields: sourceConfigFields.length > 0 ? sourceConfigFields : [{
|
|
44
41
|
name: "_placeholder",
|
|
45
42
|
type: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t
|
|
1
|
+
{"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t},\n\t\t\toptions: sources.map((source) => ({ label: labelFor(source.label), value: source.type })),\n\t\t},\n\t\t{\n\t\t\tname: 'sourceConfig',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollSourceConfig),\n\t\t\tadmin: {\n\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.optionSource),\n\t\t\t},\n\t\t\tfields:\n\t\t\t\tsourceConfigFields.length > 0\n\t\t\t\t\t? sourceConfigFields\n\t\t\t\t\t: [{ name: '_placeholder', type: 'text', admin: { hidden: true } }],\n\t\t},\n\t]\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,+BAA+B,aAAgD;CAC3F,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;CACrC,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAGT,MAAM,qBAA8B,QAAQ,SAAS,YACnD,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;EACpC,MAAM,gBACL,OAAO,MAAM,UAAU,YAAY,MAAM,UAAU,OAAO,MAAM,QAAQ,CAAC;EAC1E,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG;IACH,YAAY,SACV,MAA+C,MAAM,iBAAiB,OAAO;GAChF;EACD;CACD,CAAC,CACF;CAEA,OAAO,CACN;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,aAAa,YAAY,KAAK,2BAA2B,EAC1D;EACA,SAAS,QAAQ,KAAK,YAAY;GAAE,OAAO,SAAS,OAAO,KAAK;GAAG,OAAO,OAAO;EAAK,EAAE;CACzF,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,YAAY,OAAO,gBAAgB,QAAQ,aAAa,YAAY,EACrE;EACA,QACC,mBAAmB,SAAS,IACzB,qBACA,CAAC;GAAE,MAAM;GAAgB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE,CAAC;CACrE,CACD;AACD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Config, Payload, PayloadRequest, TaskConfig } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/closeJob.d.ts
|
|
4
|
+
declare const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
5
|
+
/** Input the enqueue path stores; the handler re-loads everything else from the DB. */
|
|
6
|
+
type PollCloseTaskInput = {
|
|
7
|
+
formId: number | string;
|
|
8
|
+
};
|
|
9
|
+
/** The slice of a loaded form the close gate reads. */
|
|
10
|
+
type PollFormLike = {
|
|
11
|
+
pollEnabled?: boolean | null;
|
|
12
|
+
poll?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
16
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
17
|
+
* triggers apply the exact same gate.
|
|
18
|
+
*/
|
|
19
|
+
declare const shouldAutoResolvePoll: (form: PollFormLike) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
22
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
23
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
24
|
+
*/
|
|
25
|
+
declare const runPollClose: (args: {
|
|
26
|
+
payload: Payload;
|
|
27
|
+
input: PollCloseTaskInput;
|
|
28
|
+
req?: PayloadRequest;
|
|
29
|
+
}) => Promise<void>;
|
|
30
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
31
|
+
declare const buildPollCloseTask: () => TaskConfig;
|
|
32
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
33
|
+
declare const registerPollCloseTask: (config: Config) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
36
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
37
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
38
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
39
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
40
|
+
*/
|
|
41
|
+
declare const enqueuePollClose: (args: {
|
|
42
|
+
payload: Payload;
|
|
43
|
+
form: {
|
|
44
|
+
id: number | string;
|
|
45
|
+
pollEnabled?: boolean | null;
|
|
46
|
+
poll?: unknown;
|
|
47
|
+
};
|
|
48
|
+
req?: PayloadRequest;
|
|
49
|
+
}) => Promise<void>;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { POLL_CLOSE_TASK_SLUG, PollCloseTaskInput, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
52
|
+
//# sourceMappingURL=closeJob.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
2
|
+
import { resolvePollOutcome } from "./resolvePollOutcome.js";
|
|
3
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
4
|
+
//#region src/poll/closeJob.ts
|
|
5
|
+
const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
6
|
+
/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */
|
|
7
|
+
const JOBS_SLUG = "payload-jobs";
|
|
8
|
+
const hasEmptyWinners = (poll) => {
|
|
9
|
+
const winners = poll.outcome?.winningValues;
|
|
10
|
+
return !Array.isArray(winners) || winners.filter((value) => typeof value === "string" && value.length > 0).length === 0;
|
|
11
|
+
};
|
|
12
|
+
const strategyOf = (poll) => typeof poll.type === "string" && poll.type.length > 0 ? poll.type : "manual";
|
|
13
|
+
/**
|
|
14
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
15
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
16
|
+
* triggers apply the exact same gate.
|
|
17
|
+
*/
|
|
18
|
+
const shouldAutoResolvePoll = (form) => {
|
|
19
|
+
const poll = pollConfigOf(form.poll);
|
|
20
|
+
return form.pollEnabled === true && poll != null && isPollClosed(poll) && hasEmptyWinners(poll) && strategyOf(poll) !== "manual";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
24
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
25
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
26
|
+
*/
|
|
27
|
+
const runPollClose = async (args) => {
|
|
28
|
+
const { payload, input, req } = args;
|
|
29
|
+
const form = await payload.findByID({
|
|
30
|
+
collection: FORMS_SLUG,
|
|
31
|
+
id: input.formId,
|
|
32
|
+
depth: 0,
|
|
33
|
+
overrideAccess: true,
|
|
34
|
+
req
|
|
35
|
+
}).catch(() => null);
|
|
36
|
+
if (!form || !shouldAutoResolvePoll(form)) return;
|
|
37
|
+
await resolvePollOutcome({
|
|
38
|
+
payload,
|
|
39
|
+
formId: input.formId,
|
|
40
|
+
req
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
44
|
+
const buildPollCloseTask = () => ({
|
|
45
|
+
slug: POLL_CLOSE_TASK_SLUG,
|
|
46
|
+
inputSchema: [{
|
|
47
|
+
name: "formId",
|
|
48
|
+
type: "text",
|
|
49
|
+
required: true
|
|
50
|
+
}],
|
|
51
|
+
handler: async ({ input, req }) => {
|
|
52
|
+
await runPollClose({
|
|
53
|
+
payload: req.payload,
|
|
54
|
+
input,
|
|
55
|
+
req
|
|
56
|
+
});
|
|
57
|
+
return { output: {} };
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
61
|
+
const registerPollCloseTask = (config) => {
|
|
62
|
+
config.jobs ??= {};
|
|
63
|
+
config.jobs.tasks ??= [];
|
|
64
|
+
config.jobs.tasks.push(buildPollCloseTask());
|
|
65
|
+
};
|
|
66
|
+
const canQueue = (payload) => typeof payload.jobs?.queue === "function";
|
|
67
|
+
/**
|
|
68
|
+
* Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload
|
|
69
|
+
* 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:
|
|
70
|
+
* a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.
|
|
71
|
+
* Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.
|
|
72
|
+
*/
|
|
73
|
+
const deletePendingCloseJobs = async (payload, formId, req) => {
|
|
74
|
+
try {
|
|
75
|
+
await payload.db.deleteMany({
|
|
76
|
+
collection: JOBS_SLUG,
|
|
77
|
+
req,
|
|
78
|
+
where: { and: [
|
|
79
|
+
{ completedAt: { exists: false } },
|
|
80
|
+
{ processing: { equals: false } },
|
|
81
|
+
{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },
|
|
82
|
+
{ "input.formId": { equals: String(formId) } }
|
|
83
|
+
] }
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
payload.logger?.error(`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
91
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
92
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
93
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
94
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
95
|
+
*/
|
|
96
|
+
const enqueuePollClose = async (args) => {
|
|
97
|
+
const { payload, form, req } = args;
|
|
98
|
+
const poll = pollConfigOf(form.poll);
|
|
99
|
+
if (form.pollEnabled !== true || !poll) return;
|
|
100
|
+
const closesAt = typeof poll.closesAt === "string" && poll.closesAt.length > 0 ? poll.closesAt : void 0;
|
|
101
|
+
if (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === "manual" || !canQueue(payload)) return;
|
|
102
|
+
const waitUntil = new Date(closesAt);
|
|
103
|
+
if (Number.isNaN(waitUntil.getTime())) return;
|
|
104
|
+
await deletePendingCloseJobs(payload, form.id, req);
|
|
105
|
+
try {
|
|
106
|
+
await payload.jobs.queue({
|
|
107
|
+
task: POLL_CLOSE_TASK_SLUG,
|
|
108
|
+
input: { formId: String(form.id) },
|
|
109
|
+
waitUntil,
|
|
110
|
+
req
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
payload.logger?.error(`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { POLL_CLOSE_TASK_SLUG, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=closeJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closeJob.js","names":[],"sources":["../../src/poll/closeJob.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\nexport const POLL_CLOSE_TASK_SLUG = 'form-builder-poll-close'\n\n/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */\nconst JOBS_SLUG = 'payload-jobs'\n\n/** Input the enqueue path stores; the handler re-loads everything else from the DB. */\nexport type PollCloseTaskInput = { formId: number | string }\n\n/** The slice of a loaded form the close gate reads. */\nexport type PollFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nconst hasEmptyWinners = (poll: PollConfigLike): boolean => {\n\tconst winners = poll.outcome?.winningValues\n\treturn (\n\t\t!Array.isArray(winners) ||\n\t\twinners.filter((value) => typeof value === 'string' && value.length > 0).length === 0\n\t)\n}\n\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/**\n * True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its\n * strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two\n * triggers apply the exact same gate.\n */\nexport const shouldAutoResolvePoll = (form: PollFormLike): boolean => {\n\tconst poll = pollConfigOf(form.poll)\n\treturn (\n\t\tform.pollEnabled === true &&\n\t\tpoll != null &&\n\t\tisPollClosed(poll) &&\n\t\thasEmptyWinners(poll) &&\n\t\tstrategyOf(poll) !== 'manual'\n\t)\n}\n\n/**\n * Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the\n * poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was\n * recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.\n */\nexport const runPollClose = async (args: {\n\tpayload: Payload\n\tinput: PollCloseTaskInput\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, input, req } = args\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: input.formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form || !shouldAutoResolvePoll(form as PollFormLike)) {\n\t\treturn\n\t}\n\tawait resolvePollOutcome({ payload, formId: input.formId, req })\n}\n\n/** Native Payload jobs task that resolves a poll's outcome once it closes. */\nexport const buildPollCloseTask = (): TaskConfig =>\n\t({\n\t\tslug: POLL_CLOSE_TASK_SLUG,\n\t\tinputSchema: [{ name: 'formId', type: 'text', required: true }],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runPollClose({ payload: req.payload, input: input as PollCloseTaskInput, req })\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerPollCloseTask = (config: Config): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildPollCloseTask())\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\n/**\n * Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload\n * 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:\n * a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.\n * Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.\n */\nconst deletePendingCloseJobs = async (\n\tpayload: Payload,\n\tformId: number | string,\n\treq?: PayloadRequest\n): Promise<void> => {\n\ttry {\n\t\tawait payload.db.deleteMany({\n\t\t\tcollection: JOBS_SLUG,\n\t\t\treq,\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ completedAt: { exists: false } },\n\t\t\t\t\t{ processing: { equals: false } },\n\t\t\t\t\t{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },\n\t\t\t\t\t{ 'input.formId': { equals: String(formId) } },\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n\n/**\n * Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`\n * job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not\n * `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read\n * fallback is the safety net. Any prior pending close job for the form is deleted first so the newest\n * `closesAt` wins. Never throws; a queue failure is logged and swallowed.\n */\nexport const enqueuePollClose = async (args: {\n\tpayload: Payload\n\tform: { id: number | string; pollEnabled?: boolean | null; poll?: unknown }\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, form, req } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn\n\t}\n\tconst closesAt =\n\t\ttypeof poll.closesAt === 'string' && poll.closesAt.length > 0 ? poll.closesAt : undefined\n\tif (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === 'manual' || !canQueue(payload)) {\n\t\treturn\n\t}\n\tconst waitUntil = new Date(closesAt)\n\tif (Number.isNaN(waitUntil.getTime())) {\n\t\treturn\n\t}\n\tawait deletePendingCloseJobs(payload, form.id, req)\n\ttry {\n\t\tawait payload.jobs.queue({\n\t\t\ttask: POLL_CLOSE_TASK_SLUG,\n\t\t\tinput: { formId: String(form.id) },\n\t\t\twaitUntil,\n\t\t\treq,\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n"],"mappings":";;;;AAKA,MAAa,uBAAuB;;AAGpC,MAAM,YAAY;AAQlB,MAAM,mBAAmB,SAAkC;CAC1D,MAAM,UAAU,KAAK,SAAS;CAC9B,OACC,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,QAAQ,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,EAAE,WAAW;AAEtF;AAEA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;AAOrE,MAAa,yBAAyB,SAAgC;CACrE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,OACC,KAAK,gBAAgB,QACrB,QAAQ,QACR,aAAa,IAAI,KACjB,gBAAgB,IAAI,KACpB,WAAW,IAAI,MAAM;AAEvB;;;;;;AAOA,MAAa,eAAe,OAAO,SAId;CACpB,MAAM,EAAE,SAAS,OAAO,QAAQ;CAChC,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI,MAAM;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EAC1F,YAAY,IAAI;CAClB,IAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAoB,GACvD;CAED,MAAM,mBAAmB;EAAE;EAAS,QAAQ,MAAM;EAAQ;CAAI,CAAC;AAChE;;AAGA,MAAa,4BACX;CACA,MAAM;CACN,aAAa,CAAC;EAAE,MAAM;EAAU,MAAM;EAAQ,UAAU;CAAK,CAAC;CAC9D,SAAS,OAAO,EAAE,OAAO,UAAU;EAClC,MAAM,aAAa;GAAE,SAAS,IAAI;GAAgB;GAA6B;EAAI,CAAC;EACpF,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,yBAAyB,WAAyB;CAC9D,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,mBAAmB,CAAC;AAC5C;AAEA,MAAM,YAAY,YAA8B,OAAO,QAAQ,MAAM,UAAU;;;;;;;AAQ/E,MAAM,yBAAyB,OAC9B,SACA,QACA,QACmB;CACnB,IAAI;EACH,MAAM,QAAQ,GAAG,WAAW;GAC3B,YAAY;GACZ;GACA,OAAO,EACN,KAAK;IACJ,EAAE,aAAa,EAAE,QAAQ,MAAM,EAAE;IACjC,EAAE,YAAY,EAAE,QAAQ,MAAM,EAAE;IAChC,EAAE,UAAU,EAAE,QAAQ,qBAAqB,EAAE;IAC7C,EAAE,gBAAgB,EAAE,QAAQ,OAAO,MAAM,EAAE,EAAE;GAC9C,EACD;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,6EAA6E,OAAO,MAAM,EAAE,IAC3F,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD;;;;;;;;AASA,MAAa,mBAAmB,OAAO,SAIlB;CACpB,MAAM,EAAE,SAAS,MAAM,QAAQ;CAC/B,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC;CAED,MAAM,WACL,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,IAAI,KAAK,WAAW,KAAA;CACjF,IAAI,CAAC,YAAY,CAAC,gBAAgB,IAAI,KAAK,WAAW,IAAI,MAAM,YAAY,CAAC,SAAS,OAAO,GAC5F;CAED,MAAM,YAAY,IAAI,KAAK,QAAQ;CACnC,IAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GACnC;CAED,MAAM,uBAAuB,SAAS,KAAK,IAAI,GAAG;CAClD,IAAI;EACH,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,QAAQ,OAAO,KAAK,EAAE,EAAE;GACjC;GACA;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,kEAAkE,OAAO,KAAK,EAAE,EAAE,IACjF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Field, Payload, PayloadRequest } from "payload";
|
|
2
2
|
|
|
3
3
|
//#region src/poll/definePollOptionSource.d.ts
|
|
4
|
-
/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.
|
|
4
|
+
/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */
|
|
5
5
|
type PollOption = {
|
|
6
6
|
label: string;
|
|
7
7
|
value: string;
|
|
@@ -26,6 +26,15 @@ type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, u
|
|
|
26
26
|
label: string;
|
|
27
27
|
config?: Field[];
|
|
28
28
|
resolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional outcome resolver: return the winning option value(s) from domain data (e.g. the
|
|
31
|
+
* recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome
|
|
32
|
+
* is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is
|
|
33
|
+
* `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit
|
|
34
|
+
* `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's
|
|
35
|
+
* resolved options before it is recorded, so each must be one of the values `resolve` yields.
|
|
36
|
+
*/
|
|
37
|
+
resolveOutcome?: (args: PollOptionResolveArgs<TConfig>) => Promise<string | string[] | undefined> | string | string[] | undefined;
|
|
29
38
|
};
|
|
30
39
|
/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */
|
|
31
40
|
type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.
|
|
1
|
+
{"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */\nexport type PollOption = { label: string; value: string }\n\nexport type PollOptionResolveArgs<\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n> = {\n\tconfig: TConfig\n\tform: { id: number | string; title?: string }\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * A poll option source type, authored once by the host: `config` is the admin `Field[]` an author\n * fills per form (e.g. an event id); `resolve` maps that config to the current options from host\n * domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where\n * the resolved values are the only accepted answers (fail closed).\n */\nexport type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {\n\ttype: string\n\tlabel: string\n\tconfig?: Field[]\n\tresolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[]\n\t/**\n\t * Optional outcome resolver: return the winning option value(s) from domain data (e.g. the\n\t * recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome\n\t * is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is\n\t * `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit\n\t * `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's\n\t * resolved options before it is recorded, so each must be one of the values `resolve` yields.\n\t */\n\tresolveOutcome?: (\n\t\targs: PollOptionResolveArgs<TConfig>\n\t) => Promise<string | string[] | undefined> | string | string[] | undefined\n}\n\n/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */\nexport type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>\n\nexport const definePollOptionSource = <\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n>(\n\tsource: PollOptionSource<TConfig>\n): PollOptionSource<TConfig> => source\n"],"mappings":";AAyCA,MAAa,0BAGZ,WAC+B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FieldAggregation } from "../aggregation/types.js";
|
|
2
|
+
import { PollConfigLike } from "../form/pollState.js";
|
|
3
|
+
import { Field, Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/poll/definePollType.d.ts
|
|
6
|
+
type PollOutcomeStrategyArgs = {
|
|
7
|
+
payload: Payload;
|
|
8
|
+
req?: PayloadRequest; /** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */
|
|
9
|
+
form: {
|
|
10
|
+
id: number | string;
|
|
11
|
+
poll?: unknown;
|
|
12
|
+
fields?: unknown;
|
|
13
|
+
} & Record<string, unknown>; /** The poll config narrowed from the form via `pollConfigOf`. */
|
|
14
|
+
poll: PollConfigLike; /** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */
|
|
15
|
+
aggregate: () => Promise<FieldAggregation | null>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`
|
|
19
|
+
* option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select
|
|
20
|
+
* picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`
|
|
21
|
+
* returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is
|
|
22
|
+
* written. Every returned value is still validated against the poll's effective options by
|
|
23
|
+
* `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.
|
|
24
|
+
*/
|
|
25
|
+
type PollOutcomeStrategy = {
|
|
26
|
+
type: string; /** i18n-key or literal (resolved like a field label), or a per-locale record. */
|
|
27
|
+
label: string | Record<string, string>;
|
|
28
|
+
config?: Field[];
|
|
29
|
+
resolveOutcome: (args: PollOutcomeStrategyArgs) => Promise<string[] | undefined> | string[] | undefined;
|
|
30
|
+
};
|
|
31
|
+
declare const definePollType: (strategy: PollOutcomeStrategy) => PollOutcomeStrategy;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PollOutcomeStrategy, PollOutcomeStrategyArgs, definePollType };
|
|
34
|
+
//# sourceMappingURL=definePollType.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definePollType.js","names":[],"sources":["../../src/poll/definePollType.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\nimport type { FieldAggregation } from '../aggregation/types'\nimport type { PollConfigLike } from '../form/pollState'\n\nexport type PollOutcomeStrategyArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */\n\tform: { id: number | string; poll?: unknown; fields?: unknown } & Record<string, unknown>\n\t/** The poll config narrowed from the form via `pollConfigOf`. */\n\tpoll: PollConfigLike\n\t/** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */\n\taggregate: () => Promise<FieldAggregation | null>\n}\n\n/**\n * An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`\n * option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select\n * picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`\n * returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is\n * written. Every returned value is still validated against the poll's effective options by\n * `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.\n */\nexport type PollOutcomeStrategy = {\n\ttype: string\n\t/** i18n-key or literal (resolved like a field label), or a per-locale record. */\n\tlabel: string | Record<string, string>\n\tconfig?: Field[]\n\tresolveOutcome: (\n\t\targs: PollOutcomeStrategyArgs\n\t) => Promise<string[] | undefined> | string[] | undefined\n}\n\nexport const definePollType = (strategy: PollOutcomeStrategy): PollOutcomeStrategy => strategy\n"],"mappings":";AAiCA,MAAa,kBAAkB,aAAuD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PollOption } from "./definePollOptionSource.js";
|
|
2
|
+
import { FieldTypeRegistry } from "../fields/registry.js";
|
|
3
|
+
import { PollOptionSourceRegistry } from "./registry.js";
|
|
4
|
+
import { Payload, PayloadRequest } from "payload";
|
|
5
|
+
|
|
6
|
+
//#region src/poll/effectivePollOptions.d.ts
|
|
7
|
+
type ResolveEffectivePollOptionsArgs = {
|
|
8
|
+
payload: Payload;
|
|
9
|
+
req?: PayloadRequest; /** A forms document (or an equivalent merged view of one during a save). */
|
|
10
|
+
form: {
|
|
11
|
+
id: number | string;
|
|
12
|
+
title?: string | null; /** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */
|
|
13
|
+
pollEnabled?: boolean | null;
|
|
14
|
+
poll?: unknown;
|
|
15
|
+
fields?: unknown;
|
|
16
|
+
}; /** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */
|
|
17
|
+
sources?: PollOptionSourceRegistry;
|
|
18
|
+
/**
|
|
19
|
+
* Field-type registry override; defaults to the one stashed on the config. Lets the results
|
|
20
|
+
* field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.
|
|
21
|
+
*/
|
|
22
|
+
fieldTypes?: FieldTypeRegistry;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
26
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
27
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
28
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
29
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
30
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
31
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
32
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
33
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
34
|
+
*/
|
|
35
|
+
declare const resolveEffectivePollOptions: (args: ResolveEffectivePollOptionsArgs) => Promise<PollOption[]>;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ResolveEffectivePollOptionsArgs, resolveEffectivePollOptions };
|
|
38
|
+
//# sourceMappingURL=effectivePollOptions.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fieldTypesOf } from "../fields/registry.js";
|
|
2
|
+
import { pollConfigOf } from "../form/pollState.js";
|
|
3
|
+
import { instanceOptionsOf } from "../fields/instanceOptions.js";
|
|
4
|
+
import { resolvePollOptions } from "./resolvePollOptions.js";
|
|
5
|
+
//#region src/poll/effectivePollOptions.ts
|
|
6
|
+
const CACHE_KEY = "formBuilderFieldOptionsCache";
|
|
7
|
+
const cacheOf = (req) => {
|
|
8
|
+
if (!req?.context) return;
|
|
9
|
+
const existing = req.context[CACHE_KEY];
|
|
10
|
+
if (existing instanceof Map) return existing;
|
|
11
|
+
const created = /* @__PURE__ */ new Map();
|
|
12
|
+
req.context[CACHE_KEY] = created;
|
|
13
|
+
return created;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
17
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
18
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
19
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
20
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
21
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
22
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
23
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
24
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
25
|
+
*/
|
|
26
|
+
const resolveEffectivePollOptions = async (args) => {
|
|
27
|
+
const { payload, req, form, sources, fieldTypes } = args;
|
|
28
|
+
const poll = pollConfigOf(form.poll);
|
|
29
|
+
if (form.pollEnabled !== true || !poll) return [];
|
|
30
|
+
const sourced = await resolvePollOptions({
|
|
31
|
+
payload,
|
|
32
|
+
req,
|
|
33
|
+
form,
|
|
34
|
+
sources
|
|
35
|
+
});
|
|
36
|
+
if (sourced) return sourced;
|
|
37
|
+
const resultsField = typeof poll.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
|
|
38
|
+
if (!resultsField) return [];
|
|
39
|
+
const instance = (Array.isArray(form.fields) ? form.fields : []).find((entry) => entry.name === resultsField);
|
|
40
|
+
if (!instance) return [];
|
|
41
|
+
const definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType);
|
|
42
|
+
if (definition?.resolveOptions) {
|
|
43
|
+
const cache = cacheOf(req);
|
|
44
|
+
const cacheKey = String(form.id);
|
|
45
|
+
const cached = cache?.get(cacheKey);
|
|
46
|
+
if (cached) return cached;
|
|
47
|
+
const options = await definition.resolveOptions({
|
|
48
|
+
instance,
|
|
49
|
+
form: {
|
|
50
|
+
id: form.id,
|
|
51
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
52
|
+
},
|
|
53
|
+
payload,
|
|
54
|
+
req
|
|
55
|
+
});
|
|
56
|
+
cache?.set(cacheKey, options);
|
|
57
|
+
return options;
|
|
58
|
+
}
|
|
59
|
+
return instanceOptionsOf(instance) ?? [];
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { resolveEffectivePollOptions };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=effectivePollOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effectivePollOptions.js","names":[],"sources":["../../src/poll/effectivePollOptions.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { instanceOptionsOf } from '../fields/instanceOptions'\nimport { type FieldTypeRegistry, fieldTypesOf } from '../fields/registry'\nimport { pollConfigOf } from '../form/pollState'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { PollOption } from './definePollOptionSource'\nimport type { PollOptionSourceRegistry } from './registry'\nimport { resolvePollOptions } from './resolvePollOptions'\n\nexport type ResolveEffectivePollOptionsArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** A forms document (or an equivalent merged view of one during a save). */\n\tform: {\n\t\tid: number | string\n\t\ttitle?: string | null\n\t\t/** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */\n\t\tpollEnabled?: boolean | null\n\t\tpoll?: unknown\n\t\tfields?: unknown\n\t}\n\t/** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */\n\tsources?: PollOptionSourceRegistry\n\t/**\n\t * Field-type registry override; defaults to the one stashed on the config. Lets the results\n\t * field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.\n\t */\n\tfieldTypes?: FieldTypeRegistry\n}\n\nconst CACHE_KEY = 'formBuilderFieldOptionsCache'\n\ntype OptionsCache = Map<string, PollOption[]>\n\nconst cacheOf = (req: PayloadRequest | undefined): OptionsCache | undefined => {\n\tif (!req?.context) {\n\t\treturn undefined\n\t}\n\tconst existing = req.context[CACHE_KEY]\n\tif (existing instanceof Map) {\n\t\treturn existing as OptionsCache\n\t}\n\tconst created: OptionsCache = new Map()\n\treq.context[CACHE_KEY] = created\n\treturn created\n}\n\n/**\n * The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options\n * when the poll has an `optionSource`, else the results field type's own {@link\n * FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a\n * custom type can enumerate the values it holds), else the authored options of the field instance.\n * Empty when the poll is disabled, no results field is set, or none of the branches yields options.\n * This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome\n * membership validation, so what the admin offers and what the server accepts cannot drift. Source\n * and resolver failures propagate so callers fail closed; the resolver branch is cached per request\n * (keyed by form id) when a `req` is provided, matching the source path.\n */\nexport const resolveEffectivePollOptions = async (\n\targs: ResolveEffectivePollOptionsArgs\n): Promise<PollOption[]> => {\n\tconst { payload, req, form, sources, fieldTypes } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn []\n\t}\n\tconst sourced = await resolvePollOptions({ payload, req, form, sources })\n\tif (sourced) {\n\t\treturn sourced\n\t}\n\tconst resultsField =\n\t\ttypeof poll.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t? poll.resultsField\n\t\t\t: undefined\n\tif (!resultsField) {\n\t\treturn []\n\t}\n\tconst instances = Array.isArray(form.fields) ? (form.fields as FormFieldInstance[]) : []\n\tconst instance = instances.find((entry) => entry.name === resultsField)\n\tif (!instance) {\n\t\treturn []\n\t}\n\tconst definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType)\n\tif (definition?.resolveOptions) {\n\t\tconst cache = cacheOf(req)\n\t\tconst cacheKey = String(form.id)\n\t\tconst cached = cache?.get(cacheKey)\n\t\tif (cached) {\n\t\t\treturn cached\n\t\t}\n\t\tconst options = await definition.resolveOptions({\n\t\t\tinstance,\n\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\tpayload,\n\t\t\treq,\n\t\t})\n\t\tcache?.set(cacheKey, options)\n\t\treturn options\n\t}\n\treturn instanceOptionsOf(instance) ?? []\n}\n"],"mappings":";;;;;AA8BA,MAAM,YAAY;AAIlB,MAAM,WAAW,QAA8D;CAC9E,IAAI,CAAC,KAAK,SACT;CAED,MAAM,WAAW,IAAI,QAAQ;CAC7B,IAAI,oBAAoB,KACvB,OAAO;CAER,MAAM,0BAAwB,IAAI,IAAI;CACtC,IAAI,QAAQ,aAAa;CACzB,OAAO;AACR;;;;;;;;;;;;AAaA,MAAa,8BAA8B,OAC1C,SAC2B;CAC3B,MAAM,EAAE,SAAS,KAAK,MAAM,SAAS,eAAe;CACpD,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO,CAAC;CAET,MAAM,UAAU,MAAM,mBAAmB;EAAE;EAAS;EAAK;EAAM;CAAQ,CAAC;CACxE,IAAI,SACH,OAAO;CAER,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;CACJ,IAAI,CAAC,cACJ,OAAO,CAAC;CAGT,MAAM,YADY,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAiC,CAAC,GAC5D,MAAM,UAAU,MAAM,SAAS,YAAY;CACtE,IAAI,CAAC,UACJ,OAAO,CAAC;CAET,MAAM,cAAc,cAAc,aAAa,OAAO,GAAG,IAAI,SAAS,SAAS;CAC/E,IAAI,YAAY,gBAAgB;EAC/B,MAAM,QAAQ,QAAQ,GAAG;EACzB,MAAM,WAAW,OAAO,KAAK,EAAE;EAC/B,MAAM,SAAS,OAAO,IAAI,QAAQ;EAClC,IAAI,QACH,OAAO;EAER,MAAM,UAAU,MAAM,WAAW,eAAe;GAC/C;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC;EACD,OAAO,IAAI,UAAU,OAAO;EAC5B,OAAO;CACR;CACA,OAAO,kBAAkB,QAAQ,KAAK,CAAC;AACxC"}
|