@10x-media/form-builder 0.1.0-beta.5 → 0.1.0-beta.7
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 +102 -0
- package/dist/actions/buildActionBlocks.js +12 -9
- package/dist/actions/buildActionBlocks.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +27 -79
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +17 -0
- package/dist/actions/builtin/emailAction.js +83 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +14 -84
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +9 -11
- package/dist/actions/builtin/index.js +11 -11
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +2 -2
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +116 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/registry.d.ts +2 -1
- package/dist/actions/registry.js +2 -6
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/task.js +5 -3
- package/dist/actions/task.js.map +1 -1
- package/dist/calc/evaluate.js +1 -2
- package/dist/calc/evaluate.js.map +1 -1
- package/dist/calc/normalizeCalc.js +1 -2
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/types.js.map +1 -1
- 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 +1 -0
- package/dist/client/EndpointOptionsSelect.js +4 -1
- package/dist/client/EndpointOptionsSelect.js.map +1 -1
- package/dist/client/FieldNameSelect.d.ts +1 -0
- package/dist/client/FieldNameSelect.js +4 -1
- package/dist/client/FieldNameSelect.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/admin.css +5 -0
- package/dist/collections/buttonFields.js +1 -1
- package/dist/collections/formSubmissions.js +4 -2
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +46 -99
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/formsEndpoints.js +101 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/conditions/evaluate.js +1 -1
- package/dist/conditions/evaluate.js.map +1 -1
- package/dist/conditions/fieldTypes.d.ts +3 -1
- package/dist/conditions/fieldTypes.js +4 -1
- package/dist/conditions/fieldTypes.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +18 -5
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/captureConsent.d.ts +21 -5
- package/dist/consent/captureConsent.js +31 -7
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +8 -0
- package/dist/consent/consentSourcesField.js +13 -2
- package/dist/consent/consentSourcesField.js.map +1 -1
- package/dist/consent/resolveConsentEntries.d.ts +1 -1
- package/dist/consent/resolveConsentEntries.js +1 -4
- package/dist/consent/resolveConsentEntries.js.map +1 -1
- package/dist/consent/resolveConsentSourcesRequest.js +0 -1
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
- package/dist/consent/resolveConsentStatements.d.ts +1 -1
- package/dist/consent/resolveConsentStatements.js.map +1 -1
- package/dist/consent/types.d.ts +13 -1
- package/dist/email/departments.d.ts +13 -9
- package/dist/email/departments.js +7 -50
- package/dist/email/departments.js.map +1 -1
- package/dist/email/departmentsField.d.ts +4 -0
- package/dist/email/departmentsField.js +5 -1
- package/dist/email/departmentsField.js.map +1 -1
- package/dist/exports/client.d.ts +4 -1
- package/dist/exports/client.js +4 -1
- package/dist/exports/react.d.ts +3 -2
- package/dist/exports/react.js +4 -3
- package/dist/exports/rsc.js +1 -1
- package/dist/exports/types.d.ts +1 -1
- package/dist/fields/buildFieldBlocks.js +4 -4
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/consent.js +6 -1
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/email.js +1 -1
- package/dist/fields/builtin/email.js.map +1 -1
- package/dist/fields/builtin/file.js +9 -3
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +1 -1
- package/dist/fields/builtin/index.js +3 -3
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +1 -1
- package/dist/fields/builtin/repeater.js +1 -1
- package/dist/fields/builtin/select.js +1 -1
- package/dist/fields/registry.d.ts +2 -1
- package/dist/fields/registry.js +2 -6
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +1 -1
- package/dist/fields/types.d.ts +9 -2
- package/dist/flow/normalizeFlow.js +7 -3
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/form/toFormDocument.d.ts +20 -3
- package/dist/form/toFormDocument.js +16 -4
- package/dist/form/toFormDocument.js.map +1 -1
- package/dist/form/types.d.ts +5 -5
- package/dist/index.d.ts +5 -193
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +228 -0
- package/dist/plugin/applyRegistryConfig.d.ts +11 -0
- package/dist/plugin/applyRegistryConfig.js +20 -0
- package/dist/plugin/applyRegistryConfig.js.map +1 -0
- package/dist/plugin/registerCollections.js +3 -1
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +3 -0
- package/dist/plugin/uploadsCollection.js +10 -1
- package/dist/plugin/uploadsCollection.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -2
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- package/dist/poll/definePollOptionSource.d.ts +2 -2
- package/dist/poll/definePollOptionSource.js.map +1 -1
- package/dist/poll/pollTypeRegistry.js +2 -4
- package/dist/poll/pollTypeRegistry.js.map +1 -1
- package/dist/poll/registry.d.ts +2 -1
- package/dist/poll/registry.js +2 -6
- package/dist/poll/registry.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +0 -1
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
- package/dist/prefill/valuesFromSearchParams.js +2 -7
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +3 -1
- package/dist/react/Form.js +93 -155
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +5 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormFields.d.ts +18 -0
- package/dist/react/FormFields.js +114 -0
- package/dist/react/FormFields.js.map +1 -0
- package/dist/react/FormLayout.d.ts +4 -2
- package/dist/react/FormLayout.js +3 -2
- package/dist/react/FormLayout.js.map +1 -1
- package/dist/react/Poll.js +13 -3
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/presentation/DialogSurface.js +1 -4
- package/dist/react/presentation/DialogSurface.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -3
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -3
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +2 -3
- 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 -3
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +6 -7
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/number.js +1 -3
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +1 -3
- package/dist/react/renderers/region.js.map +1 -1
- package/dist/react/renderers/repeater.js +20 -4
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -3
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -3
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -3
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/spam/constants.js +7 -1
- package/dist/spam/constants.js.map +1 -1
- package/dist/spam/resolveSpam.js +2 -1
- package/dist/spam/resolveSpam.js.map +1 -1
- package/dist/spam/spamGuard.js +22 -6
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +17 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +33 -26
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- package/dist/submissions/coerceBoolean.js +14 -0
- package/dist/submissions/coerceBoolean.js.map +1 -0
- package/dist/submissions/runSubmission.js +9 -8
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/validateSubmission.js +4 -2
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/translations/de.js +14 -2
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +14 -2
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +14 -2
- package/dist/translations/keys.js +14 -2
- package/dist/translations/keys.js.map +1 -1
- package/dist/translations/server.js +8 -1
- package/dist/translations/server.js.map +1 -1
- package/dist/uploads/captureFileRef.d.ts +10 -3
- package/dist/uploads/captureFileRef.js +18 -9
- package/dist/uploads/captureFileRef.js.map +1 -1
- package/dist/uploads/resolveFileRef.js +2 -1
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -1
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/emailPattern.js +10 -0
- package/dist/validation/emailPattern.js.map +1 -0
- package/dist/validation/registry.d.ts +2 -1
- package/dist/validation/registry.js +2 -9
- package/dist/validation/registry.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# @10x-media/form-builder
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.7
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Composition seam, consent re-entrance safety, and server-side recipient enforcement.
|
|
8
|
+
|
|
9
|
+
- **`<FormFields>` + `<Form header>`.** The standard field loop is now an exported `<FormFields>`
|
|
10
|
+
component, so a custom `children` layout can compose `<Form><FormSteps /><FormFields />
|
|
11
|
+
<FormControls /></Form>` without reimplementing visibility, calc, recall, and step filtering. For
|
|
12
|
+
the common case, `<Form header={<FormSteps />} />` places chrome above the fields while keeping all
|
|
13
|
+
default behavior. `recall` is now available on the form context.
|
|
14
|
+
- **Consent resolver receives the whole form document.** A multi-tenant `consent.sources` resolver
|
|
15
|
+
derives its tenant from a field on the provided doc instead of reading the form back. The consent
|
|
16
|
+
`afterRead` hook now guards against resolver re-entrance (a per-id `Set` on `req.context`), so a
|
|
17
|
+
resolver that does read the form back can no longer recurse.
|
|
18
|
+
- **`email.recipients.allowCustom: false` is enforced on the server.** When set, a non-token
|
|
19
|
+
recipient must be one of the resolved department options, validated at save and fail-closed
|
|
20
|
+
(mirroring the `from` field). The default (`allowCustom` true) is unchanged.
|
|
21
|
+
|
|
22
|
+
- Two new controls for consent reads and upload ownership.
|
|
23
|
+
|
|
24
|
+
- **`consent.resolveOnRead`** (default `true`) gates the afterRead hook that resolves each form's consent statements on every read. Set it `false` to skip that per-read source lookup (N queries on a list view) and resolve statements yourself with the exported `resolveConsentStatements` when you render a form. Submit-time proof capture is unaffected either way, since it always re-resolves from the source.
|
|
25
|
+
- **`spam.uploadOwnership`** (default `'lenient'`) controls how strictly a submitted file's upload ownership is enforced. `'strict'` additionally rejects a stamped upload whenever the submitter cannot be identified, so a deployment that always identifies requests (a trusted proxy header, or a custom `identify`) never lets an unverifiable claim capture an owned upload. `'lenient'` keeps the current fail-open behavior for unidentifiable submitters.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Consolidate the built-in email actions. `emailTeam` and `confirmation` were ~80% identical (the same recipient fields, subject/body config, from-field insertion, and send logic) duplicated across two files, each built through a five-positional-argument builder that needed a `useMaxParams` lint suppression. Both now share one `buildEmailAction` skeleton and one `EmailActionOptions` object; each action supplies only what differs (its `to` target and whether a missing recipient throws or is skipped). Behavior is unchanged, verified by both actions' full existing test suites passing against the shared implementation.
|
|
30
|
+
|
|
31
|
+
- Condition and flow correctness.
|
|
32
|
+
|
|
33
|
+
- `file` and `repeater` fields now offer a presence-only condition (exists / does not exist) in the builder, instead of the text/select operators they cannot actually satisfy (a `FileRef` object or a row array only stringifies). A presence check also treats an empty repeater as absent.
|
|
34
|
+
- Flow transition `when` clauses are laundered the same way field conditions are: a transition referencing a field that was later deleted is dropped, rather than persisting as an always-true route that force-navigates the visitor. Valid transition conditions are canonicalized to the same OR-of-ANDs shape as field conditions.
|
|
35
|
+
|
|
36
|
+
- Correctness and security hardening from an internal audit.
|
|
37
|
+
|
|
38
|
+
- **Registry key forced** on custom `actions` and `poll.sources`: a definition registered under a key that differs from its `type` no longer silently never runs.
|
|
39
|
+
- **Recipient injection closed**: a resolved `{{field}}` recipient is sanitized to a single address, dropping CR/LF header injection and any extra addresses a crafted submission value could smuggle in.
|
|
40
|
+
- **`email.recipients.fieldTokens: false`** is now enforced on the server, not just the client.
|
|
41
|
+
- **Consent integrity**: prefill and submit-time boolean coercion share one truthy allow-list, so a stray string value (e.g. from a non-React client) is never read as consent.
|
|
42
|
+
- **Cross-tenant leak closed**: the consent-sources and poll-options endpoints load the form under the caller's own read access (no `overrideAccess`), so an authenticated user cannot enumerate another tenant's form.
|
|
43
|
+
- **Rate-limit skip surfaced**: when the request identity is null the limiter still fails open, but the skip is recorded on `meta.spam.rateLimit` and warned once instead of being silent.
|
|
44
|
+
- **Honeypot collision fixed**: the decoy is submitted under a fixed reserved key, so a real field sharing the decoy's DOM input name (e.g. `website`) is never stripped or mistaken for the honeypot.
|
|
45
|
+
- **Action failures logged**: a failed post-submit email or webhook is logged per action instead of being swallowed while the submission looks fully successful.
|
|
46
|
+
|
|
47
|
+
- DX and robustness cleanup from the package audit.
|
|
48
|
+
|
|
49
|
+
- **Renderers honor the passed `id` prop.** Eleven built-in field renderers minted their own `useId()` and ignored the `id` the render host already generates and passes, so each field ran two id hooks and the documented contract prop was dead. They now use the passed `id` (the host stays the single source; a custom host can control it). `repeater` and `message` already conformed.
|
|
50
|
+
- **The overlay backdrop no longer fires `onClose` twice.** A backdrop click was handled both by the backdrop's own `onClick` and by the surface's outside-pointerdown dismiss, so it closed twice; the backdrop's handler also ignored `closeOnOutsideClick: false`. The backdrop is now purely presentational and dismissal is owned solely by the dismiss hook, which respects the flag.
|
|
51
|
+
- **Polls surface a results-load failure.** A failed results fetch was stored as an empty array and rendered as a zero-count "no votes yet" table. It now shows a localized error instead of masking the failure.
|
|
52
|
+
- **`captureFileRef` gains unit coverage** for its ownership match/mismatch, unidentified-submitter fail-open, unstamped-upload passthrough, and failed-load cases.
|
|
53
|
+
- **The consent proof documents its time-of-check/time-of-use window** (wording is read at form load but re-resolved at submit) so the intended behavior is not mistaken for a bug.
|
|
54
|
+
|
|
55
|
+
- Split the two largest source files for maintainability, with no behavior change.
|
|
56
|
+
|
|
57
|
+
- The `FormBuilderPluginOptions` type (nearly 200 lines of the plugin's public option surface, with its doc comments) moves out of `index.ts` into a dedicated `src/options.ts`, leaving `index.ts` focused on the plugin factory and the public barrel. The type is still exported under the same name (and its `PluginOptions` alias).
|
|
58
|
+
- The forms collection's built-in endpoints (poll results/options/close, plus the optional consent-sources, from-addresses, and departments endpoints) move out of `buildFormsCollection` into `buildFormsEndpoints`, so the collection builder stays focused on field and hook composition.
|
|
59
|
+
|
|
60
|
+
`index.ts` drops from ~558 to ~346 lines and `forms.ts` from ~845 to ~743; the moved code is unchanged and every existing unit, integration, and cross-DB test passes against it.
|
|
61
|
+
|
|
62
|
+
- Localize the last hard-coded English strings in the public form renderer. The file field's attached-file indicator, in-flight "Uploading" status, upload-failure fallback, and remove control, plus the form's close-control label, success message, and submit-failure fallback, now resolve through the same `t` the rest of the renderer already uses (bundled `en`/`de`, host-overridable per locale) instead of baked-in English. A German (or any) `t` now localizes them; behavior under the default English `t` is unchanged.
|
|
63
|
+
|
|
64
|
+
- Consolidate the registry and constant plumbing.
|
|
65
|
+
|
|
66
|
+
- The field-type, validation-rule, action, poll-source, and poll-type registries all resolved their opt-in override map (`false` removes, `true` keeps, a definition adds/replaces with `type` forced to the key) with their own copy of the same loop. That loop now lives in one shared `applyRegistryConfig` helper, so the merge semantics are defined once instead of five times.
|
|
67
|
+
- Definition `label` contracts are aligned: actions and poll option sources now accept a per-locale `Record<string, string>` label in addition to a string key/literal, matching poll types. All three resolve through one shared `resolveDefinitionLabel` helper. Widening only, so existing string labels are unaffected.
|
|
68
|
+
- The email-format regex (duplicated between the `email` field type and the `email` validation rule) and the calc recursion-depth guard (duplicated between the calc parser and evaluator) are each a single shared constant now, so the pair can no longer drift.
|
|
69
|
+
|
|
70
|
+
- Repeater correctness fixes.
|
|
71
|
+
|
|
72
|
+
- Multi-step forms now validate a step's repeater sub-fields on **Next**, so an invalid required sub-field can no longer be skipped past; it surfaces inline, mirroring submit and the server. `goNext` also gains a re-entrancy guard so a double-click cannot push the same step onto history twice.
|
|
73
|
+
- Editing a repeater sub-field clears its stale server-side error immediately, instead of leaving it shown until the next submit.
|
|
74
|
+
- Repeater rows carry a stable React key, so removing a middle row no longer strands a stateful sub-renderer's local state (e.g. the file renderer's filename) onto the wrong row.
|
|
75
|
+
|
|
76
|
+
## 0.1.0-beta.6
|
|
77
|
+
|
|
78
|
+
### Minor Changes
|
|
79
|
+
|
|
80
|
+
- Email actions and consent polish, plus paddle-worldwide handoff asks.
|
|
81
|
+
|
|
82
|
+
- **New creatable multi-recipient email field.** To, Reply-to, CC, and BCC on the Email Team and
|
|
83
|
+
Confirmation actions accept multiple recipients as native badges, mixing picked department
|
|
84
|
+
addresses, free-typed emails, and `{{field}}` recipient tokens (drag-reorderable, `admin.width`
|
|
85
|
+
aware). Configurable via `email.recipients` (`allowCustom`, `fieldTokens`, `tokenFieldTypes`).
|
|
86
|
+
- **Half-width pairing** of the email address fields (To/Reply-to, CC/BCC).
|
|
87
|
+
- **Consent field polish:** no more redundant description (the source statement is the content); the
|
|
88
|
+
field block title and the source array rows now show the consent source's name; the condensed
|
|
89
|
+
department-emails array centers its remove control on the input.
|
|
90
|
+
- **Consent statements on every read.** An `afterRead` hook resolves statements onto the form doc, so
|
|
91
|
+
consent renders wherever the form is fetched (modal/client, not only RSC); `toFormDocument` falls
|
|
92
|
+
back to the doc-carried statements.
|
|
93
|
+
- **`toFormDocument`** accepts a populated redirect `reference` (`value: string | number | object |
|
|
94
|
+
null`) and an optional `resolveRedirect` seam that fills `redirect.url`.
|
|
95
|
+
- **Versioned consent proof.** Submissions snapshot the agreed wording via `consent.snapshot`
|
|
96
|
+
(default `both`: a statement hash, the plain text, and the source name), for an audit trail
|
|
97
|
+
independent of later source edits.
|
|
98
|
+
- **File MIME picker** constrained to the host upload collection's `mimeTypes` (with an optional
|
|
99
|
+
`uploads.mimeTypes` override).
|
|
100
|
+
|
|
101
|
+
**BREAKING:** `emailTeam`/`confirmation` `to`, `cc`, `bcc`, and `replyTo` are now `string[]` (`text`
|
|
102
|
+
`hasMany`). A legacy single-string value reads as one recipient; Postgres consumers regenerate a
|
|
103
|
+
migration.
|
|
104
|
+
|
|
3
105
|
## 0.1.0-beta.5
|
|
4
106
|
|
|
5
107
|
### Minor Changes
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveDefinitionLabel } from "../translations/server.js";
|
|
2
2
|
//#region src/actions/buildActionBlocks.ts
|
|
3
3
|
/** One authoring block per registered action type: the action's own config fields. */
|
|
4
4
|
const buildActionBlocks = (registry) => {
|
|
5
5
|
const blocks = [];
|
|
6
|
-
for (const definition of registry.values())
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
for (const definition of registry.values()) {
|
|
7
|
+
const label = resolveDefinitionLabel(definition.label);
|
|
8
|
+
blocks.push({
|
|
9
|
+
slug: definition.type,
|
|
10
|
+
labels: {
|
|
11
|
+
singular: label,
|
|
12
|
+
plural: label
|
|
13
|
+
},
|
|
14
|
+
fields: definition.config ?? []
|
|
15
|
+
});
|
|
16
|
+
}
|
|
14
17
|
return blocks;
|
|
15
18
|
};
|
|
16
19
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildActionBlocks.js","names":[],"sources":["../../src/actions/buildActionBlocks.ts"],"sourcesContent":["import type { Block } from 'payload'\nimport {
|
|
1
|
+
{"version":3,"file":"buildActionBlocks.js","names":[],"sources":["../../src/actions/buildActionBlocks.ts"],"sourcesContent":["import type { Block } from 'payload'\nimport { resolveDefinitionLabel } from '../translations/server'\nimport type { ActionRegistry } from './registry'\n\n/** One authoring block per registered action type: the action's own config fields. */\nexport const buildActionBlocks = (registry: ActionRegistry): Block[] => {\n\tconst blocks: Block[] = []\n\tfor (const definition of registry.values()) {\n\t\tconst label = resolveDefinitionLabel(definition.label)\n\t\tblocks.push({\n\t\t\tslug: definition.type,\n\t\t\tlabels: { singular: label, plural: label },\n\t\t\tfields: definition.config ?? [],\n\t\t})\n\t}\n\treturn blocks\n}\n"],"mappings":";;;AAKA,MAAa,qBAAqB,aAAsC;CACvE,MAAM,SAAkB,CAAC;CACzB,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,MAAM,QAAQ,uBAAuB,WAAW,KAAK;EACrD,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU;IAAO,QAAQ;GAAM;GACzC,QAAQ,WAAW,UAAU,CAAC;EAC/B,CAAC;CACF;CACA,OAAO;AACR"}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { fieldNamesOfType } from "../../fields/fieldNamesOfType.js";
|
|
2
|
-
import { localizedIf } from "../../fields/localizedIf.js";
|
|
3
|
-
import { interpolate } from "../../recall/interpolate.js";
|
|
4
2
|
import { keys } from "../../translations/keys.js";
|
|
5
3
|
import { asTranslate, labelFor } from "../../translations/server.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { buildFromField } from "../fromAddresses.js";
|
|
4
|
+
import { firstAddress } from "../emailRecipients.js";
|
|
5
|
+
import { buildEmailAction } from "./emailAction.js";
|
|
9
6
|
//#region src/actions/builtin/confirmation.ts
|
|
10
7
|
const TO_FIELD_REF = "@10x-media/form-builder/client#FieldNameSelect";
|
|
11
8
|
/**
|
|
@@ -17,87 +14,38 @@ const validateToField = (value, { data, req }) => {
|
|
|
17
14
|
return fieldNamesOfType(data && typeof data === "object" ? data.fields : void 0, ["email"]).includes(value) ? true : asTranslate(req.t)(keys.validationEmailFieldUnknown);
|
|
18
15
|
};
|
|
19
16
|
/**
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `richText.editor` option). `fromAddresses`, when given (the plugin's `email.fromAddresses`
|
|
26
|
-
* option), adds a `from` select sourced from the host resolver; absent, no `from` field exists and
|
|
27
|
-
* every send uses the email adapter's default sender.
|
|
17
|
+
* The `confirmation` action: its target is `toField`, naming the form's own email field the
|
|
18
|
+
* confirmation is sent to (a `FieldNameSelect`, not a recipient list). The resolved address is
|
|
19
|
+
* sanitized to a single one (dropping CR/LF header injection and any smuggled extra addresses); an
|
|
20
|
+
* unset field or an unresolvable value is a silent skip. Everything else (from/cc/bcc/replyTo,
|
|
21
|
+
* subject, body, localization) is the shared email-action skeleton.
|
|
28
22
|
*/
|
|
29
|
-
const buildConfirmation = (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
admin: { components: { Field: {
|
|
23
|
+
const buildConfirmation = (options) => {
|
|
24
|
+
const toField = {
|
|
25
|
+
name: "toField",
|
|
26
|
+
type: "text",
|
|
27
|
+
label: labelFor(keys.actionConfigToField),
|
|
28
|
+
admin: {
|
|
29
|
+
width: "50%",
|
|
30
|
+
components: { Field: {
|
|
38
31
|
path: TO_FIELD_REF,
|
|
39
32
|
clientProps: {
|
|
40
33
|
types: ["email"],
|
|
41
34
|
descriptionKey: keys.actionConfigToFieldDescription
|
|
42
35
|
}
|
|
43
|
-
} }
|
|
44
|
-
validate: validateToField
|
|
36
|
+
} }
|
|
45
37
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
...localizedIf(localize)
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "replyTo",
|
|
61
|
-
type: "text",
|
|
62
|
-
label: labelFor(keys.actionConfigReplyTo),
|
|
63
|
-
...localizedIf(localize)
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "subject",
|
|
67
|
-
type: "text",
|
|
68
|
-
label: labelFor(keys.actionConfigSubject),
|
|
69
|
-
...localizedIf(localize)
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "body",
|
|
73
|
-
type: "richText",
|
|
74
|
-
label: labelFor(keys.actionConfigBody),
|
|
75
|
-
admin: { description: labelFor(keys.actionConfigBodyDescription) },
|
|
76
|
-
...localizedIf(localize),
|
|
77
|
-
...editor ? { editor } : {}
|
|
78
|
-
}
|
|
79
|
-
],
|
|
80
|
-
run: async (args) => {
|
|
81
|
-
const { config, values, payload, renderBody } = args;
|
|
82
|
-
if (!config.toField) return;
|
|
83
|
-
const resolve = resolverFor(values);
|
|
84
|
-
const to = resolve(config.toField);
|
|
85
|
-
if (!to) return;
|
|
86
|
-
if (typeof payload.sendEmail !== "function") throw new Error("confirmation: no email adapter configured");
|
|
87
|
-
const subject = interpolate(config.subject ?? "", resolve);
|
|
88
|
-
const html = await renderBody(config.body);
|
|
89
|
-
await payload.sendEmail({
|
|
90
|
-
to,
|
|
91
|
-
subject,
|
|
92
|
-
html,
|
|
93
|
-
...config.from ? { from: config.from } : {},
|
|
94
|
-
...config.cc ? { cc: interpolate(config.cc, resolve) } : {},
|
|
95
|
-
...config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {},
|
|
96
|
-
...config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
buildConfirmation(true);
|
|
38
|
+
validate: validateToField
|
|
39
|
+
};
|
|
40
|
+
return buildEmailAction(options, {
|
|
41
|
+
type: "confirmation",
|
|
42
|
+
label: keys.actionConfirmation,
|
|
43
|
+
target: () => toField,
|
|
44
|
+
resolveTo: (config, resolve) => config.toField ? firstAddress(resolve(config.toField)) : void 0,
|
|
45
|
+
onMissingTo: "skip"
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
buildConfirmation({ localize: true });
|
|
101
49
|
//#endregion
|
|
102
50
|
export { buildConfirmation };
|
|
103
51
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmation.js","names":[],"sources":["../../../src/actions/builtin/confirmation.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"confirmation.js","names":[],"sources":["../../../src/actions/builtin/confirmation.ts"],"sourcesContent":["import type { Field, PayloadRequest } from 'payload'\nimport { fieldNamesOfType } from '../../fields/fieldNamesOfType'\nimport { keys } from '../../translations/keys'\nimport { asTranslate, labelFor } from '../../translations/server'\nimport { firstAddress } from '../emailRecipients'\nimport { buildEmailAction, type EmailActionConfig, type EmailActionOptions } from './emailAction'\n\ntype ConfirmationConfig = EmailActionConfig & { toField?: string }\n\nconst TO_FIELD_REF = '@10x-media/form-builder/client#FieldNameSelect'\n\n/**\n * Validates the confirmation action's `toField`: unset is fine, otherwise it must name an\n * existing `email`-type field on the form. Exported for unit testing.\n */\nexport const validateToField = (\n\tvalue: unknown,\n\t{ data, req }: { data?: unknown; req: PayloadRequest }\n): string | true => {\n\tif (typeof value !== 'string' || value.length === 0) {\n\t\treturn true\n\t}\n\tconst fields =\n\t\tdata && typeof data === 'object' ? (data as Record<string, unknown>).fields : undefined\n\treturn fieldNamesOfType(fields, ['email']).includes(value)\n\t\t? true\n\t\t: asTranslate(req.t)(keys.validationEmailFieldUnknown)\n}\n\n/**\n * The `confirmation` action: its target is `toField`, naming the form's own email field the\n * confirmation is sent to (a `FieldNameSelect`, not a recipient list). The resolved address is\n * sanitized to a single one (dropping CR/LF header injection and any smuggled extra addresses); an\n * unset field or an unresolvable value is a silent skip. Everything else (from/cc/bcc/replyTo,\n * subject, body, localization) is the shared email-action skeleton.\n */\nexport const buildConfirmation = (options: EmailActionOptions) => {\n\tconst toField: Field = {\n\t\tname: 'toField',\n\t\ttype: 'text',\n\t\tlabel: labelFor(keys.actionConfigToField),\n\t\tadmin: {\n\t\t\twidth: '50%',\n\t\t\tcomponents: {\n\t\t\t\tField: {\n\t\t\t\t\tpath: TO_FIELD_REF,\n\t\t\t\t\tclientProps: { types: ['email'], descriptionKey: keys.actionConfigToFieldDescription },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tvalidate: validateToField,\n\t}\n\treturn buildEmailAction<ConfirmationConfig>(options, {\n\t\ttype: 'confirmation',\n\t\tlabel: keys.actionConfirmation,\n\t\ttarget: () => toField,\n\t\tresolveTo: (config, resolve) =>\n\t\t\tconfig.toField ? firstAddress(resolve(config.toField)) : undefined,\n\t\tonMissingTo: 'skip',\n\t})\n}\n\nexport const confirmation = buildConfirmation({ localize: true })\n"],"mappings":";;;;;;AASA,MAAM,eAAe;;;;;AAMrB,MAAa,mBACZ,OACA,EAAE,MAAM,UACW;CACnB,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GACjD,OAAO;CAIR,OAAO,iBADN,QAAQ,OAAO,SAAS,WAAY,KAAiC,SAAS,KAAA,GAC/C,CAAC,OAAO,CAAC,EAAE,SAAS,KAAK,IACtD,OACA,YAAY,IAAI,CAAC,EAAE,KAAK,2BAA2B;AACvD;;;;;;;;AASA,MAAa,qBAAqB,YAAgC;CACjE,MAAM,UAAiB;EACtB,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,mBAAmB;EACxC,OAAO;GACN,OAAO;GACP,YAAY,EACX,OAAO;IACN,MAAM;IACN,aAAa;KAAE,OAAO,CAAC,OAAO;KAAG,gBAAgB,KAAK;IAA+B;GACtF,EACD;EACD;EACA,UAAU;CACX;CACA,OAAO,iBAAqC,SAAS;EACpD,MAAM;EACN,OAAO,KAAK;EACZ,cAAc;EACd,YAAY,QAAQ,YACnB,OAAO,UAAU,aAAa,QAAQ,OAAO,OAAO,CAAC,IAAI,KAAA;EAC1D,aAAa;CACd,CAAC;AACF;AAE4B,kBAAkB,EAAE,UAAU,KAAK,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FromAddressesResolver } from "../fromAddresses.js";
|
|
2
|
+
import { DepartmentEmailsResolver } from "../../email/departments.js";
|
|
3
|
+
import { RecipientsConfig } from "../emailRecipients.js";
|
|
4
|
+
import { Field, RichTextField } from "payload";
|
|
5
|
+
|
|
6
|
+
//#region src/actions/builtin/emailAction.d.ts
|
|
7
|
+
/** The plugin-derived options every built-in email action is built from (was five positional args). */
|
|
8
|
+
type EmailActionOptions = {
|
|
9
|
+
localize: boolean;
|
|
10
|
+
editor?: RichTextField['editor'];
|
|
11
|
+
fromAddresses?: FromAddressesResolver;
|
|
12
|
+
departments?: DepartmentEmailsResolver;
|
|
13
|
+
recipients?: RecipientsConfig;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { EmailActionOptions };
|
|
17
|
+
//# sourceMappingURL=emailAction.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { keys } from "../../translations/keys.js";
|
|
2
|
+
import { labelFor } from "../../translations/server.js";
|
|
3
|
+
import { localizedIf } from "../../fields/localizedIf.js";
|
|
4
|
+
import { interpolate } from "../../recall/interpolate.js";
|
|
5
|
+
import { buildRecipientField, resolveRecipients } from "../emailRecipients.js";
|
|
6
|
+
import { resolverFor } from "../body/serializeBody.js";
|
|
7
|
+
import { defineAction } from "../defineAction.js";
|
|
8
|
+
import { buildFromField } from "../fromAddresses.js";
|
|
9
|
+
//#region src/actions/builtin/emailAction.ts
|
|
10
|
+
/**
|
|
11
|
+
* The shared skeleton of the built-in email actions (`emailTeam`, `confirmation`): an identical
|
|
12
|
+
* config (a first row pairing the action's target with `replyTo`, an optional `from` select, a
|
|
13
|
+
* cc/bcc row, a subject, and a rich text body, content and recipient fields carrying `localized`
|
|
14
|
+
* when `localize`) and an identical send (interpolate the subject, render the body, resolve
|
|
15
|
+
* cc/bcc/replyTo, and hand a single comma-joined string per list to `payload.sendEmail`). Only the
|
|
16
|
+
* primary `to` target and its missing-value behavior differ, threaded through `spec`.
|
|
17
|
+
*/
|
|
18
|
+
const buildEmailAction = (options, spec) => {
|
|
19
|
+
const { localize, editor, fromAddresses, departments, recipients } = options;
|
|
20
|
+
const endpoint = departments ? "departments" : void 0;
|
|
21
|
+
const recip = (name, labelKey) => buildRecipientField(name, labelKey, localize, {
|
|
22
|
+
endpoint,
|
|
23
|
+
recipients,
|
|
24
|
+
width: "50%",
|
|
25
|
+
departments
|
|
26
|
+
});
|
|
27
|
+
return defineAction({
|
|
28
|
+
type: spec.type,
|
|
29
|
+
label: spec.label,
|
|
30
|
+
config: [
|
|
31
|
+
{
|
|
32
|
+
type: "row",
|
|
33
|
+
fields: [spec.target(recip), recip("replyTo", keys.actionConfigReplyTo)]
|
|
34
|
+
},
|
|
35
|
+
...fromAddresses ? [buildFromField(fromAddresses)] : [],
|
|
36
|
+
{
|
|
37
|
+
type: "row",
|
|
38
|
+
fields: [recip("cc", keys.actionConfigCc), recip("bcc", keys.actionConfigBcc)]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "subject",
|
|
42
|
+
type: "text",
|
|
43
|
+
label: labelFor(keys.actionConfigSubject),
|
|
44
|
+
...localizedIf(localize)
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "body",
|
|
48
|
+
type: "richText",
|
|
49
|
+
label: labelFor(keys.actionConfigBody),
|
|
50
|
+
admin: { description: labelFor(keys.actionConfigBodyDescription) },
|
|
51
|
+
...localizedIf(localize),
|
|
52
|
+
...editor ? { editor } : {}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
run: async ({ config, values, payload, renderBody }) => {
|
|
56
|
+
const resolve = resolverFor(values);
|
|
57
|
+
const to = spec.resolveTo(config, resolve);
|
|
58
|
+
if (!to) {
|
|
59
|
+
if (spec.onMissingTo === "throw") throw new Error(`${spec.type}: missing "to" address`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (typeof payload.sendEmail !== "function") throw new Error(`${spec.type}: no email adapter configured`);
|
|
63
|
+
const subject = interpolate(config.subject ?? "", resolve);
|
|
64
|
+
const html = await renderBody(config.body);
|
|
65
|
+
const cc = resolveRecipients(config.cc, resolve);
|
|
66
|
+
const bcc = resolveRecipients(config.bcc, resolve);
|
|
67
|
+
const replyTo = resolveRecipients(config.replyTo, resolve);
|
|
68
|
+
await payload.sendEmail({
|
|
69
|
+
to,
|
|
70
|
+
subject,
|
|
71
|
+
html,
|
|
72
|
+
...config.from ? { from: config.from } : {},
|
|
73
|
+
...cc ? { cc } : {},
|
|
74
|
+
...bcc ? { bcc } : {},
|
|
75
|
+
...replyTo ? { replyTo } : {}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { buildEmailAction };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=emailAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emailAction.js","names":[],"sources":["../../../src/actions/builtin/emailAction.ts"],"sourcesContent":["import type { Field, RichTextField } from 'payload'\nimport type { DepartmentEmailsResolver } from '../../email/departments'\nimport { localizedIf } from '../../fields/localizedIf'\nimport { interpolate } from '../../recall/interpolate'\nimport { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { resolverFor } from '../body/serializeBody'\nimport { type ActionDefinition, defineAction } from '../defineAction'\nimport { buildRecipientField, type RecipientsConfig, resolveRecipients } from '../emailRecipients'\nimport { buildFromField, type FromAddressesResolver } from '../fromAddresses'\n\n/** The plugin-derived options every built-in email action is built from (was five positional args). */\nexport type EmailActionOptions = {\n\tlocalize: boolean\n\teditor?: RichTextField['editor']\n\tfromAddresses?: FromAddressesResolver\n\tdepartments?: DepartmentEmailsResolver\n\trecipients?: RecipientsConfig\n}\n\n/** The config fields shared by every built-in email action (each action adds its own `to` target). */\nexport type EmailActionConfig = {\n\tfrom?: string\n\tcc?: string[]\n\tbcc?: string[]\n\treplyTo?: string[]\n\tsubject?: string\n\tbody?: unknown\n}\n\n/** Builds a recipient-list field (`to`/`cc`/`bcc`/`replyTo`) with the shared width, endpoint, and options. */\ntype RecipientFieldBuilder = (name: string, labelKey: string) => Field\n\ntype Resolver = ReturnType<typeof resolverFor>\n\n/** What distinguishes one email action from another: identity, its primary target, and how it resolves/guards that target. */\ntype EmailActionSpec<TConfig extends EmailActionConfig> = {\n\ttype: string\n\tlabel: string\n\t/** The first cell of the opening row (paired with `replyTo`): a recipient list, or an email-field select. */\n\ttarget: (recip: RecipientFieldBuilder) => Field\n\t/** Resolve the primary `to` from the stored config, or `undefined` when there is no recipient. */\n\tresolveTo: (config: TConfig, resolve: Resolver) => string | undefined\n\t/** With no resolved `to`, `emailTeam` treats it as a misconfiguration (`throw`), `confirmation` as a silent skip. */\n\tonMissingTo: 'throw' | 'skip'\n}\n\n/**\n * The shared skeleton of the built-in email actions (`emailTeam`, `confirmation`): an identical\n * config (a first row pairing the action's target with `replyTo`, an optional `from` select, a\n * cc/bcc row, a subject, and a rich text body, content and recipient fields carrying `localized`\n * when `localize`) and an identical send (interpolate the subject, render the body, resolve\n * cc/bcc/replyTo, and hand a single comma-joined string per list to `payload.sendEmail`). Only the\n * primary `to` target and its missing-value behavior differ, threaded through `spec`.\n */\nexport const buildEmailAction = <TConfig extends EmailActionConfig>(\n\toptions: EmailActionOptions,\n\tspec: EmailActionSpec<TConfig>\n): ActionDefinition<TConfig> => {\n\tconst { localize, editor, fromAddresses, departments, recipients } = options\n\tconst endpoint = departments ? 'departments' : undefined\n\tconst recip: RecipientFieldBuilder = (name, labelKey) =>\n\t\tbuildRecipientField(name, labelKey, localize, {\n\t\t\tendpoint,\n\t\t\trecipients,\n\t\t\twidth: '50%',\n\t\t\tdepartments,\n\t\t})\n\treturn defineAction<TConfig>({\n\t\ttype: spec.type,\n\t\tlabel: spec.label,\n\t\tconfig: [\n\t\t\t{\n\t\t\t\ttype: 'row',\n\t\t\t\tfields: [spec.target(recip), recip('replyTo', keys.actionConfigReplyTo)],\n\t\t\t},\n\t\t\t...(fromAddresses ? [buildFromField(fromAddresses)] : []),\n\t\t\t{\n\t\t\t\ttype: 'row',\n\t\t\t\tfields: [recip('cc', keys.actionConfigCc), recip('bcc', keys.actionConfigBcc)],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'subject',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigSubject),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'body',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelFor(keys.actionConfigBody),\n\t\t\t\tadmin: { description: labelFor(keys.actionConfigBodyDescription) },\n\t\t\t\t...localizedIf(localize),\n\t\t\t\t...(editor ? { editor } : {}),\n\t\t\t},\n\t\t],\n\t\trun: async ({ config, values, payload, renderBody }) => {\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst to = spec.resolveTo(config, resolve)\n\t\t\tif (!to) {\n\t\t\t\tif (spec.onMissingTo === 'throw') {\n\t\t\t\t\tthrow new Error(`${spec.type}: missing \"to\" address`)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (typeof payload.sendEmail !== 'function') {\n\t\t\t\tthrow new Error(`${spec.type}: no email adapter configured`)\n\t\t\t}\n\n\t\t\tconst subject = interpolate(config.subject ?? '', resolve)\n\t\t\tconst html = await renderBody(config.body)\n\t\t\tconst cc = resolveRecipients(config.cc, resolve)\n\t\t\tconst bcc = resolveRecipients(config.bcc, resolve)\n\t\t\tconst replyTo = resolveRecipients(config.replyTo, resolve)\n\n\t\t\t// `from` was validated at save time against `fromAddresses(req)`; not re-checked here\n\t\t\t// (the job's `req` may differ from the authoring admin's, and the config is admin-authored,\n\t\t\t// not visitor-controlled), so the stored value is forwarded verbatim.\n\t\t\tawait payload.sendEmail({\n\t\t\t\tto,\n\t\t\t\tsubject,\n\t\t\t\thtml,\n\t\t\t\t...(config.from ? { from: config.from } : {}),\n\t\t\t\t...(cc ? { cc } : {}),\n\t\t\t\t...(bcc ? { bcc } : {}),\n\t\t\t\t...(replyTo ? { replyTo } : {}),\n\t\t\t})\n\t\t},\n\t})\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,MAAa,oBACZ,SACA,SAC+B;CAC/B,MAAM,EAAE,UAAU,QAAQ,eAAe,aAAa,eAAe;CACrE,MAAM,WAAW,cAAc,gBAAgB,KAAA;CAC/C,MAAM,SAAgC,MAAM,aAC3C,oBAAoB,MAAM,UAAU,UAAU;EAC7C;EACA;EACA,OAAO;EACP;CACD,CAAC;CACF,OAAO,aAAsB;EAC5B,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,QAAQ;GACP;IACC,MAAM;IACN,QAAQ,CAAC,KAAK,OAAO,KAAK,GAAG,MAAM,WAAW,KAAK,mBAAmB,CAAC;GACxE;GACA,GAAI,gBAAgB,CAAC,eAAe,aAAa,CAAC,IAAI,CAAC;GACvD;IACC,MAAM;IACN,QAAQ,CAAC,MAAM,MAAM,KAAK,cAAc,GAAG,MAAM,OAAO,KAAK,eAAe,CAAC;GAC9E;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,mBAAmB;IACxC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,gBAAgB;IACrC,OAAO,EAAE,aAAa,SAAS,KAAK,2BAA2B,EAAE;IACjE,GAAG,YAAY,QAAQ;IACvB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC5B;EACD;EACA,KAAK,OAAO,EAAE,QAAQ,QAAQ,SAAS,iBAAiB;GACvD,MAAM,UAAU,YAAY,MAAM;GAClC,MAAM,KAAK,KAAK,UAAU,QAAQ,OAAO;GACzC,IAAI,CAAC,IAAI;IACR,IAAI,KAAK,gBAAgB,SACxB,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,uBAAuB;IAErD;GACD;GACA,IAAI,OAAO,QAAQ,cAAc,YAChC,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,8BAA8B;GAG5D,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;GACzD,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI;GACzC,MAAM,KAAK,kBAAkB,OAAO,IAAI,OAAO;GAC/C,MAAM,MAAM,kBAAkB,OAAO,KAAK,OAAO;GACjD,MAAM,UAAU,kBAAkB,OAAO,SAAS,OAAO;GAKzD,MAAM,QAAQ,UAAU;IACvB;IACA;IACA;IACA,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;IAC3C,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;IACnB,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;IACrB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC9B,CAAC;EACF;CACD,CAAC;AACF"}
|
|
@@ -1,91 +1,21 @@
|
|
|
1
|
-
import { localizedIf } from "../../fields/localizedIf.js";
|
|
2
|
-
import { interpolate } from "../../recall/interpolate.js";
|
|
3
1
|
import { keys } from "../../translations/keys.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { defineAction } from "../defineAction.js";
|
|
7
|
-
import { buildFromField } from "../fromAddresses.js";
|
|
8
|
-
import { buildToField } from "../../email/departments.js";
|
|
2
|
+
import { resolveRecipients } from "../emailRecipients.js";
|
|
3
|
+
import { buildEmailAction } from "./emailAction.js";
|
|
9
4
|
//#region src/actions/builtin/emailTeam.ts
|
|
10
5
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* send
|
|
14
|
-
*
|
|
15
|
-
* merge tags work; a comma-separated list is forwarded as-is, since `payload.sendEmail`
|
|
16
|
-
* (nodemailer) accepts one directly. `editor` overrides the body field's Lexical/richText editor
|
|
17
|
-
* (from the plugin's `richText.editor` option). `fromAddresses`, when given (the plugin's
|
|
18
|
-
* `email.fromAddresses` option), adds a `from` select sourced from the host resolver; absent, no
|
|
19
|
-
* `from` field exists and every send uses the email adapter's default sender. `departments`, when
|
|
20
|
-
* given (the plugin's `email.departments` option), turns `to` into a select whose options are the
|
|
21
|
-
* host's resolved departments; absent, `to` stays a plain (localized) text field.
|
|
6
|
+
* The `emailTeam` action: its target is a `to` recipient list (`RecipientsSelect`, `string[]`) of
|
|
7
|
+
* literal emails, picked department addresses, or `{{field}}` tokens resolved from the submission at
|
|
8
|
+
* send. An empty `to` is a misconfiguration and throws. Everything else (from/cc/bcc/replyTo, subject,
|
|
9
|
+
* body, localization) is the shared email-action skeleton.
|
|
22
10
|
*/
|
|
23
|
-
const buildEmailTeam = (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
type: "emailTeam",
|
|
32
|
-
label: keys.actionEmailTeam,
|
|
33
|
-
config: [
|
|
34
|
-
toField,
|
|
35
|
-
...fromAddresses ? [buildFromField(fromAddresses)] : [],
|
|
36
|
-
{
|
|
37
|
-
name: "cc",
|
|
38
|
-
type: "text",
|
|
39
|
-
label: labelFor(keys.actionConfigCc),
|
|
40
|
-
...localizedIf(localize)
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: "bcc",
|
|
44
|
-
type: "text",
|
|
45
|
-
label: labelFor(keys.actionConfigBcc),
|
|
46
|
-
...localizedIf(localize)
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "replyTo",
|
|
50
|
-
type: "text",
|
|
51
|
-
label: labelFor(keys.actionConfigReplyTo),
|
|
52
|
-
...localizedIf(localize)
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "subject",
|
|
56
|
-
type: "text",
|
|
57
|
-
label: labelFor(keys.actionConfigSubject),
|
|
58
|
-
...localizedIf(localize)
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: "body",
|
|
62
|
-
type: "richText",
|
|
63
|
-
label: labelFor(keys.actionConfigBody),
|
|
64
|
-
admin: { description: labelFor(keys.actionConfigBodyDescription) },
|
|
65
|
-
...localizedIf(localize),
|
|
66
|
-
...editor ? { editor } : {}
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
run: async (args) => {
|
|
70
|
-
const { config, values, payload, renderBody } = args;
|
|
71
|
-
if (!config.to) throw new Error("emailTeam: missing \"to\" address");
|
|
72
|
-
if (typeof payload.sendEmail !== "function") throw new Error("emailTeam: no email adapter configured");
|
|
73
|
-
const resolve = resolverFor(values);
|
|
74
|
-
const subject = interpolate(config.subject ?? "", resolve);
|
|
75
|
-
const html = await renderBody(config.body);
|
|
76
|
-
await payload.sendEmail({
|
|
77
|
-
to: config.to,
|
|
78
|
-
subject,
|
|
79
|
-
html,
|
|
80
|
-
...config.from ? { from: config.from } : {},
|
|
81
|
-
...config.cc ? { cc: interpolate(config.cc, resolve) } : {},
|
|
82
|
-
...config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {},
|
|
83
|
-
...config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
};
|
|
88
|
-
buildEmailTeam(true);
|
|
11
|
+
const buildEmailTeam = (options) => buildEmailAction(options, {
|
|
12
|
+
type: "emailTeam",
|
|
13
|
+
label: keys.actionEmailTeam,
|
|
14
|
+
target: (recip) => recip("to", keys.actionConfigTo),
|
|
15
|
+
resolveTo: (config, resolve) => resolveRecipients(config.to, resolve),
|
|
16
|
+
onMissingTo: "throw"
|
|
17
|
+
});
|
|
18
|
+
buildEmailTeam({ localize: true });
|
|
89
19
|
//#endregion
|
|
90
20
|
export { buildEmailTeam };
|
|
91
21
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailTeam.js","names":[],"sources":["../../../src/actions/builtin/emailTeam.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"emailTeam.js","names":[],"sources":["../../../src/actions/builtin/emailTeam.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { resolveRecipients } from '../emailRecipients'\nimport { buildEmailAction, type EmailActionConfig, type EmailActionOptions } from './emailAction'\n\ntype EmailTeamConfig = EmailActionConfig & { to?: string[] }\n\n/**\n * The `emailTeam` action: its target is a `to` recipient list (`RecipientsSelect`, `string[]`) of\n * literal emails, picked department addresses, or `{{field}}` tokens resolved from the submission at\n * send. An empty `to` is a misconfiguration and throws. Everything else (from/cc/bcc/replyTo, subject,\n * body, localization) is the shared email-action skeleton.\n */\nexport const buildEmailTeam = (options: EmailActionOptions) =>\n\tbuildEmailAction<EmailTeamConfig>(options, {\n\t\ttype: 'emailTeam',\n\t\tlabel: keys.actionEmailTeam,\n\t\ttarget: (recip) => recip('to', keys.actionConfigTo),\n\t\tresolveTo: (config, resolve) => resolveRecipients(config.to, resolve),\n\t\tonMissingTo: 'throw',\n\t})\n\nexport const emailTeam = buildEmailTeam({ localize: true })\n"],"mappings":";;;;;;;;;;AAYA,MAAa,kBAAkB,YAC9B,iBAAkC,SAAS;CAC1C,MAAM;CACN,OAAO,KAAK;CACZ,SAAS,UAAU,MAAM,MAAM,KAAK,cAAc;CAClD,YAAY,QAAQ,YAAY,kBAAkB,OAAO,IAAI,OAAO;CACpE,aAAa;AACd,CAAC;AAEuB,eAAe,EAAE,UAAU,KAAK,CAAC"}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { AnyActionDefinition } from "../defineAction.js";
|
|
2
|
-
import {
|
|
3
|
-
import { DepartmentEmailsResolver } from "../../email/departments.js";
|
|
4
|
-
import { RichTextField } from "payload";
|
|
2
|
+
import { EmailActionOptions } from "./emailAction.js";
|
|
5
3
|
|
|
6
4
|
//#region src/actions/builtin/index.d.ts
|
|
7
5
|
/**
|
|
8
6
|
* Built-in action definitions with content-bearing config fields (email subjects and rich text
|
|
9
|
-
* bodies) carrying `localized: true` when `localize` is true. `editor`, when given,
|
|
10
|
-
* the Lexical/richText editor on both action bodies (the plugin passes
|
|
11
|
-
* `richText.bodyEditor ?? richText.editor`). `fromAddresses
|
|
12
|
-
* `email.fromAddresses` option)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* bodies) carrying `localized: true` when `options.localize` is true. `options.editor`, when given,
|
|
8
|
+
* overrides the Lexical/richText editor on both action bodies (the plugin passes
|
|
9
|
+
* `richText.bodyEditor ?? richText.editor`). `options.fromAddresses` (the plugin's
|
|
10
|
+
* `email.fromAddresses` option) adds a `from` select to both email actions. `options.departments`
|
|
11
|
+
* (the plugin's `email.departments` option) turns the recipient selects into pickers over the host's
|
|
12
|
+
* resolved departments. `signedWebhook` has no content fields and is shared static.
|
|
15
13
|
*/
|
|
16
|
-
declare const buildDefaultActionDefinitions: (
|
|
14
|
+
declare const buildDefaultActionDefinitions: (options: EmailActionOptions) => Record<string, AnyActionDefinition>;
|
|
17
15
|
/**
|
|
18
16
|
* Prebuilt with content localization on; with `localizeContent: false`, derive overrides from
|
|
19
|
-
* `buildDefaultActionDefinitions(false)` instead so a spread does not reintroduce `localized`.
|
|
17
|
+
* `buildDefaultActionDefinitions({ localize: false })` instead so a spread does not reintroduce `localized`.
|
|
20
18
|
*/
|
|
21
19
|
declare const defaultActionDefinitions: Record<string, AnyActionDefinition>;
|
|
22
20
|
//#endregion
|