@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.
Files changed (213) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/actions/buildActionBlocks.js +12 -9
  3. package/dist/actions/buildActionBlocks.js.map +1 -1
  4. package/dist/actions/builtin/confirmation.js +27 -79
  5. package/dist/actions/builtin/confirmation.js.map +1 -1
  6. package/dist/actions/builtin/emailAction.d.ts +17 -0
  7. package/dist/actions/builtin/emailAction.js +83 -0
  8. package/dist/actions/builtin/emailAction.js.map +1 -0
  9. package/dist/actions/builtin/emailTeam.js +14 -84
  10. package/dist/actions/builtin/emailTeam.js.map +1 -1
  11. package/dist/actions/builtin/index.d.ts +9 -11
  12. package/dist/actions/builtin/index.js +11 -11
  13. package/dist/actions/builtin/index.js.map +1 -1
  14. package/dist/actions/defineAction.d.ts +2 -2
  15. package/dist/actions/defineAction.js.map +1 -1
  16. package/dist/actions/emailRecipients.d.ts +12 -0
  17. package/dist/actions/emailRecipients.js +116 -0
  18. package/dist/actions/emailRecipients.js.map +1 -0
  19. package/dist/actions/registry.d.ts +2 -1
  20. package/dist/actions/registry.js +2 -6
  21. package/dist/actions/registry.js.map +1 -1
  22. package/dist/actions/task.js +5 -3
  23. package/dist/actions/task.js.map +1 -1
  24. package/dist/calc/evaluate.js +1 -2
  25. package/dist/calc/evaluate.js.map +1 -1
  26. package/dist/calc/normalizeCalc.js +1 -2
  27. package/dist/calc/normalizeCalc.js.map +1 -1
  28. package/dist/calc/types.js.map +1 -1
  29. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  30. package/dist/client/ConsentBlockLabel.js +71 -0
  31. package/dist/client/ConsentBlockLabel.js.map +1 -0
  32. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  33. package/dist/client/ConsentSourceRowLabel.js +21 -0
  34. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  35. package/dist/client/EndpointOptionsSelect.d.ts +1 -0
  36. package/dist/client/EndpointOptionsSelect.js +4 -1
  37. package/dist/client/EndpointOptionsSelect.js.map +1 -1
  38. package/dist/client/FieldNameSelect.d.ts +1 -0
  39. package/dist/client/FieldNameSelect.js +4 -1
  40. package/dist/client/FieldNameSelect.js.map +1 -1
  41. package/dist/client/RecipientsSelect.d.ts +31 -0
  42. package/dist/client/RecipientsSelect.js +174 -0
  43. package/dist/client/RecipientsSelect.js.map +1 -0
  44. package/dist/client/admin.css +5 -0
  45. package/dist/collections/buttonFields.js +1 -1
  46. package/dist/collections/formSubmissions.js +4 -2
  47. package/dist/collections/formSubmissions.js.map +1 -1
  48. package/dist/collections/forms.js +46 -99
  49. package/dist/collections/forms.js.map +1 -1
  50. package/dist/collections/formsEndpoints.js +101 -0
  51. package/dist/collections/formsEndpoints.js.map +1 -0
  52. package/dist/conditions/evaluate.js +1 -1
  53. package/dist/conditions/evaluate.js.map +1 -1
  54. package/dist/conditions/fieldTypes.d.ts +3 -1
  55. package/dist/conditions/fieldTypes.js +4 -1
  56. package/dist/conditions/fieldTypes.js.map +1 -1
  57. package/dist/conditions/normalizeConditions.js +18 -5
  58. package/dist/conditions/normalizeConditions.js.map +1 -1
  59. package/dist/consent/captureConsent.d.ts +21 -5
  60. package/dist/consent/captureConsent.js +31 -7
  61. package/dist/consent/captureConsent.js.map +1 -1
  62. package/dist/consent/consentSourcesField.d.ts +8 -0
  63. package/dist/consent/consentSourcesField.js +13 -2
  64. package/dist/consent/consentSourcesField.js.map +1 -1
  65. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  66. package/dist/consent/resolveConsentEntries.js +1 -4
  67. package/dist/consent/resolveConsentEntries.js.map +1 -1
  68. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  69. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  70. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  71. package/dist/consent/resolveConsentStatements.js.map +1 -1
  72. package/dist/consent/types.d.ts +13 -1
  73. package/dist/email/departments.d.ts +13 -9
  74. package/dist/email/departments.js +7 -50
  75. package/dist/email/departments.js.map +1 -1
  76. package/dist/email/departmentsField.d.ts +4 -0
  77. package/dist/email/departmentsField.js +5 -1
  78. package/dist/email/departmentsField.js.map +1 -1
  79. package/dist/exports/client.d.ts +4 -1
  80. package/dist/exports/client.js +4 -1
  81. package/dist/exports/react.d.ts +3 -2
  82. package/dist/exports/react.js +4 -3
  83. package/dist/exports/rsc.js +1 -1
  84. package/dist/exports/types.d.ts +1 -1
  85. package/dist/fields/buildFieldBlocks.js +4 -4
  86. package/dist/fields/buildFieldBlocks.js.map +1 -1
  87. package/dist/fields/builtin/consent.js +6 -1
  88. package/dist/fields/builtin/consent.js.map +1 -1
  89. package/dist/fields/builtin/email.js +1 -1
  90. package/dist/fields/builtin/email.js.map +1 -1
  91. package/dist/fields/builtin/file.js +9 -3
  92. package/dist/fields/builtin/file.js.map +1 -1
  93. package/dist/fields/builtin/index.d.ts +1 -1
  94. package/dist/fields/builtin/index.js +3 -3
  95. package/dist/fields/builtin/index.js.map +1 -1
  96. package/dist/fields/builtin/message.js +1 -1
  97. package/dist/fields/builtin/repeater.js +1 -1
  98. package/dist/fields/builtin/select.js +1 -1
  99. package/dist/fields/registry.d.ts +2 -1
  100. package/dist/fields/registry.js +2 -6
  101. package/dist/fields/registry.js.map +1 -1
  102. package/dist/fields/sharedConfig.js +1 -1
  103. package/dist/fields/types.d.ts +9 -2
  104. package/dist/flow/normalizeFlow.js +7 -3
  105. package/dist/flow/normalizeFlow.js.map +1 -1
  106. package/dist/form/toFormDocument.d.ts +20 -3
  107. package/dist/form/toFormDocument.js +16 -4
  108. package/dist/form/toFormDocument.js.map +1 -1
  109. package/dist/form/types.d.ts +5 -5
  110. package/dist/index.d.ts +5 -193
  111. package/dist/index.js +18 -8
  112. package/dist/index.js.map +1 -1
  113. package/dist/options.d.ts +228 -0
  114. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  115. package/dist/plugin/applyRegistryConfig.js +20 -0
  116. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  117. package/dist/plugin/registerCollections.js +3 -1
  118. package/dist/plugin/registerCollections.js.map +1 -1
  119. package/dist/plugin/uploadsCollection.d.ts +3 -0
  120. package/dist/plugin/uploadsCollection.js +10 -1
  121. package/dist/plugin/uploadsCollection.js.map +1 -1
  122. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  123. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  124. package/dist/poll/definePollOptionSource.d.ts +2 -2
  125. package/dist/poll/definePollOptionSource.js.map +1 -1
  126. package/dist/poll/pollTypeRegistry.js +2 -4
  127. package/dist/poll/pollTypeRegistry.js.map +1 -1
  128. package/dist/poll/registry.d.ts +2 -1
  129. package/dist/poll/registry.js +2 -6
  130. package/dist/poll/registry.js.map +1 -1
  131. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  132. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  133. package/dist/prefill/valuesFromSearchParams.js +2 -7
  134. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  135. package/dist/react/Form.d.ts +3 -1
  136. package/dist/react/Form.js +93 -155
  137. package/dist/react/Form.js.map +1 -1
  138. package/dist/react/FormContext.d.ts +5 -1
  139. package/dist/react/FormContext.js.map +1 -1
  140. package/dist/react/FormFields.d.ts +18 -0
  141. package/dist/react/FormFields.js +114 -0
  142. package/dist/react/FormFields.js.map +1 -0
  143. package/dist/react/FormLayout.d.ts +4 -2
  144. package/dist/react/FormLayout.js +3 -2
  145. package/dist/react/FormLayout.js.map +1 -1
  146. package/dist/react/Poll.js +13 -3
  147. package/dist/react/Poll.js.map +1 -1
  148. package/dist/react/presentation/DialogSurface.js +1 -4
  149. package/dist/react/presentation/DialogSurface.js.map +1 -1
  150. package/dist/react/renderers/calculation.js +1 -3
  151. package/dist/react/renderers/calculation.js.map +1 -1
  152. package/dist/react/renderers/checkbox.js +1 -3
  153. package/dist/react/renderers/checkbox.js.map +1 -1
  154. package/dist/react/renderers/consent.js +2 -3
  155. package/dist/react/renderers/consent.js.map +1 -1
  156. package/dist/react/renderers/date.js +1 -3
  157. package/dist/react/renderers/date.js.map +1 -1
  158. package/dist/react/renderers/email.js +1 -3
  159. package/dist/react/renderers/email.js.map +1 -1
  160. package/dist/react/renderers/file.js +6 -7
  161. package/dist/react/renderers/file.js.map +1 -1
  162. package/dist/react/renderers/number.js +1 -3
  163. package/dist/react/renderers/number.js.map +1 -1
  164. package/dist/react/renderers/region.js +1 -3
  165. package/dist/react/renderers/region.js.map +1 -1
  166. package/dist/react/renderers/repeater.js +20 -4
  167. package/dist/react/renderers/repeater.js.map +1 -1
  168. package/dist/react/renderers/select.js +1 -3
  169. package/dist/react/renderers/select.js.map +1 -1
  170. package/dist/react/renderers/text.js +1 -3
  171. package/dist/react/renderers/text.js.map +1 -1
  172. package/dist/react/renderers/textarea.js +1 -3
  173. package/dist/react/renderers/textarea.js.map +1 -1
  174. package/dist/spam/constants.js +7 -1
  175. package/dist/spam/constants.js.map +1 -1
  176. package/dist/spam/resolveSpam.js +2 -1
  177. package/dist/spam/resolveSpam.js.map +1 -1
  178. package/dist/spam/spamGuard.js +22 -6
  179. package/dist/spam/spamGuard.js.map +1 -1
  180. package/dist/spam/types.d.ts +17 -2
  181. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  182. package/dist/submissions/SubmissionAnswersClient.js +33 -26
  183. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  184. package/dist/submissions/coerceBoolean.js +14 -0
  185. package/dist/submissions/coerceBoolean.js.map +1 -0
  186. package/dist/submissions/runSubmission.js +9 -8
  187. package/dist/submissions/runSubmission.js.map +1 -1
  188. package/dist/submissions/validateSubmission.js +4 -2
  189. package/dist/submissions/validateSubmission.js.map +1 -1
  190. package/dist/translations/de.js +14 -2
  191. package/dist/translations/de.js.map +1 -1
  192. package/dist/translations/en.js +14 -2
  193. package/dist/translations/en.js.map +1 -1
  194. package/dist/translations/keys.d.ts +14 -2
  195. package/dist/translations/keys.js +14 -2
  196. package/dist/translations/keys.js.map +1 -1
  197. package/dist/translations/server.js +8 -1
  198. package/dist/translations/server.js.map +1 -1
  199. package/dist/uploads/captureFileRef.d.ts +10 -3
  200. package/dist/uploads/captureFileRef.js +18 -9
  201. package/dist/uploads/captureFileRef.js.map +1 -1
  202. package/dist/uploads/resolveFileRef.js +2 -1
  203. package/dist/uploads/resolveFileRef.js.map +1 -1
  204. package/dist/validation/builtin/email.js +1 -1
  205. package/dist/validation/builtin/email.js.map +1 -1
  206. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  207. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  208. package/dist/validation/emailPattern.js +10 -0
  209. package/dist/validation/emailPattern.js.map +1 -0
  210. package/dist/validation/registry.d.ts +2 -1
  211. package/dist/validation/registry.js +2 -9
  212. package/dist/validation/registry.js.map +1 -1
  213. 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 { labelFor } from "../translations/server.js";
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()) blocks.push({
7
- slug: definition.type,
8
- labels: {
9
- singular: labelFor(definition.label),
10
- plural: labelFor(definition.label)
11
- },
12
- fields: definition.config ?? []
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 { labelFor } 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\tblocks.push({\n\t\t\tslug: definition.type,\n\t\t\tlabels: { singular: labelFor(definition.label), plural: labelFor(definition.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,GACxC,OAAO,KAAK;EACX,MAAM,WAAW;EACjB,QAAQ;GAAE,UAAU,SAAS,WAAW,KAAK;GAAG,QAAQ,SAAS,WAAW,KAAK;EAAE;EACnF,QAAQ,WAAW,UAAU,CAAC;CAC/B,CAAC;CAEF,OAAO;AACR"}
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 { resolverFor } from "../body/serializeBody.js";
7
- import { defineAction } from "../defineAction.js";
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
- * `subject` and `body` are email content and follow `localize`; `toField` is an identifier and
21
- * `from` an address, neither of which ever does. `cc`, `bcc`, and `replyTo` are addresses that
22
- * follow `localize` (like `subject`), each interpolated the same way so `{{field}}` merge tags
23
- * work; a comma-separated list is forwarded as-is, since `payload.sendEmail` (nodemailer) accepts
24
- * one directly. `editor` overrides the body field's Lexical/richText editor (from the plugin's
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 = (localize, editor, fromAddresses) => defineAction({
30
- type: "confirmation",
31
- label: keys.actionConfirmation,
32
- config: [
33
- {
34
- name: "toField",
35
- type: "text",
36
- label: labelFor(keys.actionConfigToField),
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
- ...fromAddresses ? [buildFromField(fromAddresses)] : [],
47
- {
48
- name: "cc",
49
- type: "text",
50
- label: labelFor(keys.actionConfigCc),
51
- ...localizedIf(localize)
52
- },
53
- {
54
- name: "bcc",
55
- type: "text",
56
- label: labelFor(keys.actionConfigBcc),
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 { PayloadRequest, RichTextField } from 'payload'\nimport { fieldNamesOfType } from '../../fields/fieldNamesOfType'\nimport { localizedIf } from '../../fields/localizedIf'\nimport { interpolate } from '../../recall/interpolate'\nimport { keys } from '../../translations/keys'\nimport { asTranslate, labelFor } from '../../translations/server'\nimport { resolverFor } from '../body/serializeBody'\nimport { defineAction } from '../defineAction'\nimport { buildFromField, type FromAddressesResolver } from '../fromAddresses'\n\ntype ConfirmationConfig = {\n\ttoField?: string\n\tfrom?: string\n\tcc?: string\n\tbcc?: string\n\treplyTo?: string\n\tsubject?: string\n\tbody?: unknown\n}\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 * `subject` and `body` are email content and follow `localize`; `toField` is an identifier and\n * `from` an address, neither of which ever does. `cc`, `bcc`, and `replyTo` are addresses that\n * follow `localize` (like `subject`), each interpolated the same way so `{{field}}` merge tags\n * work; a comma-separated list is forwarded as-is, since `payload.sendEmail` (nodemailer) accepts\n * one directly. `editor` overrides the body field's Lexical/richText editor (from the plugin's\n * `richText.editor` option). `fromAddresses`, when given (the plugin's `email.fromAddresses`\n * option), adds a `from` select sourced from the host resolver; absent, no `from` field exists and\n * every send uses the email adapter's default sender.\n */\nexport const buildConfirmation = (\n\tlocalize: boolean,\n\teditor?: RichTextField['editor'],\n\tfromAddresses?: FromAddressesResolver\n) =>\n\tdefineAction<ConfirmationConfig>({\n\t\ttype: 'confirmation',\n\t\tlabel: keys.actionConfirmation,\n\t\tconfig: [\n\t\t\t{\n\t\t\t\tname: 'toField',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigToField),\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: {\n\t\t\t\t\t\tField: {\n\t\t\t\t\t\t\tpath: TO_FIELD_REF,\n\t\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\t\ttypes: ['email'],\n\t\t\t\t\t\t\t\tdescriptionKey: keys.actionConfigToFieldDescription,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tvalidate: validateToField,\n\t\t\t},\n\t\t\t...(fromAddresses ? [buildFromField(fromAddresses)] : []),\n\t\t\t{\n\t\t\t\tname: 'cc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigCc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'bcc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigBcc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'replyTo',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigReplyTo),\n\t\t\t\t...localizedIf(localize),\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 (args) => {\n\t\t\tconst { config, values, payload, renderBody } = args\n\n\t\t\tif (!config.toField) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst to = resolve(config.toField)\n\n\t\t\tif (!to) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (typeof payload.sendEmail !== 'function') {\n\t\t\t\tthrow new Error('confirmation: 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\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\n\t\t\t// admin-authored, 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...(config.cc ? { cc: interpolate(config.cc, resolve) } : {}),\n\t\t\t\t...(config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {}),\n\t\t\t\t...(config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}),\n\t\t\t})\n\t\t},\n\t})\n\nexport const confirmation = buildConfirmation(true)\n"],"mappings":";;;;;;;;;AAoBA,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;;;;;;;;;;;AAYA,MAAa,qBACZ,UACA,QACA,kBAEA,aAAiC;CAChC,MAAM;CACN,OAAO,KAAK;CACZ,QAAQ;EACP;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,mBAAmB;GACxC,OAAO,EACN,YAAY,EACX,OAAO;IACN,MAAM;IACN,aAAa;KACZ,OAAO,CAAC,OAAO;KACf,gBAAgB,KAAK;IACtB;GACD,EACD,EACD;GACA,UAAU;EACX;EACA,GAAI,gBAAgB,CAAC,eAAe,aAAa,CAAC,IAAI,CAAC;EACvD;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,cAAc;GACnC,GAAG,YAAY,QAAQ;EACxB;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,eAAe;GACpC,GAAG,YAAY,QAAQ;EACxB;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,mBAAmB;GACxC,GAAG,YAAY,QAAQ;EACxB;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,mBAAmB;GACxC,GAAG,YAAY,QAAQ;EACxB;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,gBAAgB;GACrC,OAAO,EAAE,aAAa,SAAS,KAAK,2BAA2B,EAAE;GACjE,GAAG,YAAY,QAAQ;GACvB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC5B;CACD;CACA,KAAK,OAAO,SAAS;EACpB,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;EAEhD,IAAI,CAAC,OAAO,SACX;EAGD,MAAM,UAAU,YAAY,MAAM;EAClC,MAAM,KAAK,QAAQ,OAAO,OAAO;EAEjC,IAAI,CAAC,IACJ;EAGD,IAAI,OAAO,QAAQ,cAAc,YAChC,MAAM,IAAI,MAAM,2CAA2C;EAG5D,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;EACzD,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI;EAKzC,MAAM,QAAQ,UAAU;GACvB;GACA;GACA;GACA,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;GAC3C,GAAI,OAAO,KAAK,EAAE,IAAI,YAAY,OAAO,IAAI,OAAO,EAAE,IAAI,CAAC;GAC3D,GAAI,OAAO,MAAM,EAAE,KAAK,YAAY,OAAO,KAAK,OAAO,EAAE,IAAI,CAAC;GAC9D,GAAI,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,SAAS,OAAO,EAAE,IAAI,CAAC;EAC3E,CAAC;CACF;AACD,CAAC;AAE0B,kBAAkB,IAAI"}
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 { labelFor } from "../../translations/server.js";
5
- import { resolverFor } from "../body/serializeBody.js";
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
- * `subject` and `body` are email content and follow `localize`; `from` is an address that never
12
- * does. `to` is localized (following `localize`): a submission's locale selects the stored `to` at
13
- * send, so a German submission routes to the German address. `cc`, `bcc`, and `replyTo` follow the
14
- * same localized-address posture as `to`, each interpolated the same as `subject` so `{{field}}`
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 = (localize, editor, fromAddresses, departments) => {
24
- const toField = departments ? buildToField(departments, localize) : {
25
- name: "to",
26
- type: "text",
27
- label: labelFor(keys.actionConfigTo),
28
- ...localizedIf(localize)
29
- };
30
- return defineAction({
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 type { RichTextField, TextField } from 'payload'\nimport type { DepartmentEmailsResolver } from '../../email/departments'\nimport { buildToField } 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 { defineAction } from '../defineAction'\nimport { buildFromField, type FromAddressesResolver } from '../fromAddresses'\n\ntype EmailTeamConfig = {\n\tto?: string\n\tfrom?: string\n\tcc?: string\n\tbcc?: string\n\treplyTo?: string\n\tsubject?: string\n\tbody?: unknown\n}\n\n/**\n * `subject` and `body` are email content and follow `localize`; `from` is an address that never\n * does. `to` is localized (following `localize`): a submission's locale selects the stored `to` at\n * send, so a German submission routes to the German address. `cc`, `bcc`, and `replyTo` follow the\n * same localized-address posture as `to`, each interpolated the same as `subject` so `{{field}}`\n * merge tags work; a comma-separated list is forwarded as-is, since `payload.sendEmail`\n * (nodemailer) accepts one directly. `editor` overrides the body field's Lexical/richText editor\n * (from the plugin's `richText.editor` option). `fromAddresses`, when given (the plugin's\n * `email.fromAddresses` option), adds a `from` select sourced from the host resolver; absent, no\n * `from` field exists and every send uses the email adapter's default sender. `departments`, when\n * given (the plugin's `email.departments` option), turns `to` into a select whose options are the\n * host's resolved departments; absent, `to` stays a plain (localized) text field.\n */\n// biome-ignore lint/complexity/useMaxParams: positional args mirror the fromAddresses threading (localize, editor, fromAddresses, departments)\nexport const buildEmailTeam = (\n\tlocalize: boolean,\n\teditor?: RichTextField['editor'],\n\tfromAddresses?: FromAddressesResolver,\n\tdepartments?: DepartmentEmailsResolver\n) => {\n\tconst toField: TextField = departments\n\t\t? buildToField(departments, localize)\n\t\t: { name: 'to', type: 'text', label: labelFor(keys.actionConfigTo), ...localizedIf(localize) }\n\treturn defineAction<EmailTeamConfig>({\n\t\ttype: 'emailTeam',\n\t\tlabel: keys.actionEmailTeam,\n\t\tconfig: [\n\t\t\ttoField,\n\t\t\t...(fromAddresses ? [buildFromField(fromAddresses)] : []),\n\t\t\t{\n\t\t\t\tname: 'cc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigCc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'bcc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigBcc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'replyTo',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigReplyTo),\n\t\t\t\t...localizedIf(localize),\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 (args) => {\n\t\t\tconst { config, values, payload, renderBody } = args\n\n\t\t\tif (!config.to) {\n\t\t\t\tthrow new Error('emailTeam: missing \"to\" address')\n\t\t\t}\n\n\t\t\tif (typeof payload.sendEmail !== 'function') {\n\t\t\t\tthrow new Error('emailTeam: no email adapter configured')\n\t\t\t}\n\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst subject = interpolate(config.subject ?? '', resolve)\n\t\t\tconst html = await renderBody(config.body)\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\n\t\t\t// admin-authored, not visitor-controlled), so the stored value is forwarded verbatim.\n\t\t\tawait payload.sendEmail({\n\t\t\t\tto: config.to,\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...(config.cc ? { cc: interpolate(config.cc, resolve) } : {}),\n\t\t\t\t...(config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {}),\n\t\t\t\t...(config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}),\n\t\t\t})\n\t\t},\n\t})\n}\n\nexport const emailTeam = buildEmailTeam(true)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAa,kBACZ,UACA,QACA,eACA,gBACI;CACJ,MAAM,UAAqB,cACxB,aAAa,aAAa,QAAQ,IAClC;EAAE,MAAM;EAAM,MAAM;EAAQ,OAAO,SAAS,KAAK,cAAc;EAAG,GAAG,YAAY,QAAQ;CAAE;CAC9F,OAAO,aAA8B;EACpC,MAAM;EACN,OAAO,KAAK;EACZ,QAAQ;GACP;GACA,GAAI,gBAAgB,CAAC,eAAe,aAAa,CAAC,IAAI,CAAC;GACvD;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,cAAc;IACnC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,eAAe;IACpC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,mBAAmB;IACxC,GAAG,YAAY,QAAQ;GACxB;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,SAAS;GACpB,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;GAEhD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,mCAAiC;GAGlD,IAAI,OAAO,QAAQ,cAAc,YAChC,MAAM,IAAI,MAAM,wCAAwC;GAGzD,MAAM,UAAU,YAAY,MAAM;GAClC,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;GACzD,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI;GAKzC,MAAM,QAAQ,UAAU;IACvB,IAAI,OAAO;IACX;IACA;IACA,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;IAC3C,GAAI,OAAO,KAAK,EAAE,IAAI,YAAY,OAAO,IAAI,OAAO,EAAE,IAAI,CAAC;IAC3D,GAAI,OAAO,MAAM,EAAE,KAAK,YAAY,OAAO,KAAK,OAAO,EAAE,IAAI,CAAC;IAC9D,GAAI,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,SAAS,OAAO,EAAE,IAAI,CAAC;GAC3E,CAAC;EACF;CACD,CAAC;AACF;AAEyB,eAAe,IAAI"}
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 { FromAddressesResolver } from "../fromAddresses.js";
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, overrides
10
- * the Lexical/richText editor on both action bodies (the plugin passes
11
- * `richText.bodyEditor ?? richText.editor`). `fromAddresses`, when given (the plugin's
12
- * `email.fromAddresses` option), adds a `from` select to both email actions. `departments`, when
13
- * given (the plugin's `email.departments` option), turns the `emailTeam` `to` into a select over the
14
- * host's resolved departments. `signedWebhook` has no content fields and is shared static.
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: (localize: boolean, editor?: RichTextField["editor"], fromAddresses?: FromAddressesResolver, departments?: DepartmentEmailsResolver) => Record<string, AnyActionDefinition>;
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