@10x-media/form-builder 0.1.0-beta.23 → 0.1.0-beta.25
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 +14 -0
- package/dist/actions/body/serializeBody.d.ts +7 -1
- package/dist/actions/body/serializeBody.js +3 -1
- package/dist/actions/body/serializeBody.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +3 -1
- package/dist/actions/builtin/emailAction.js +11 -3
- package/dist/actions/builtin/emailAction.js.map +1 -1
- package/dist/actions/emailRender.d.ts +27 -0
- package/dist/actions/runActions.js +9 -7
- package/dist/actions/runActions.js.map +1 -1
- package/dist/collections/formSubmissions.js +2 -1
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +12 -2
- package/dist/react/Form.d.ts +6 -1
- package/dist/react/Form.js +6 -3
- package/dist/react/Form.js.map +1 -1
- package/dist/react/submitForm.d.ts +15 -3
- package/dist/react/submitForm.js +5 -3
- package/dist/react/submitForm.js.map +1 -1
- package/dist/spam/spamGuard.js +1 -1
- package/dist/submissions/submissionLocale.js +18 -0
- package/dist/submissions/submissionLocale.js.map +1 -0
- package/dist/submissions/validateSubmission.js +6 -3
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/voteChange.js +21 -0
- package/dist/submissions/voteChange.js.map +1 -0
- package/dist/submissions/voteChangeEndpoint.js +2 -1
- package/dist/submissions/voteChangeEndpoint.js.map +1 -1
- package/dist/submissions/votedCookie.js +2 -14
- package/dist/submissions/votedCookie.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @10x-media/form-builder
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.25
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Emails follow the visitor's locale. `<Form>` now sends an explicit `locale` prop with the submission as `?locale=` (and hands it to a custom `onSubmit` as `locale`), so the submission stores the visitor's locale instead of the host's default, and the post-submit actions, the confirmation email included, render its subject and body in it. Without the prop nothing changes. The server clamps the visitor-controlled locale before anything reads it: a localized host keeps only its configured locale codes (`all`, `*`, and unknown codes become the default locale), a host without localization keeps any plain language tag and otherwise stores `en`. A custom `richText.serialize` also receives that `locale` and the `actionType` rendering the body (`emailTeam`, `confirmation`, or a custom action's type), so an email wrapper can localize its own strings and give the visitor's confirmation a different layout than the team notification.
|
|
8
|
+
|
|
9
|
+
- Add `email.render`, a hook producing the final html of every `emailTeam` and `confirmation` email from the already serialized body, so a host can wrap emails in a branded, localized layout without re-implementing the rich text pipeline. It receives the serialized `html`, the raw `body`, the interpolated `subject`, the `actionType`, and the submission context (`locale`, `form`, `submissionId`, `values`, `descriptors`, `context`, `payload`, `req`). It runs after `richText.serialize`, so the two compose.
|
|
10
|
+
|
|
11
|
+
## 0.1.0-beta.24
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- The `./react` client entry no longer imports `node:crypto`. The built-in `notAlreadySubmitted` rule read the vote-change key from `votedCookie`, which signs cookies with `node:crypto`, so every host app shipped a browser crypto polyfill (and its `eval`-based `vm` shim) with the form renderer. The key and `voteChangeTargetOf` now live in a crypto-free module; `votedCookie` re-exports both, so existing imports keep working.
|
|
16
|
+
|
|
3
17
|
## 0.1.0-beta.23
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
|
@@ -25,12 +25,18 @@ type SerializeBodyArgs = {
|
|
|
25
25
|
descriptors: SubmissionDescriptor[];
|
|
26
26
|
form: SerializeBodyForm;
|
|
27
27
|
req?: PayloadRequest;
|
|
28
|
+
/**
|
|
29
|
+
* The submission's own stored locale, the one the form (and so `body`) was loaded at. Use it for
|
|
30
|
+
* a wrapper's own strings rather than `req.locale`, which on the queued path is the job runner's.
|
|
31
|
+
*/
|
|
32
|
+
locale: string; /** The `blockType` of the action rendering this body (e.g. `emailTeam`, `confirmation`). */
|
|
33
|
+
actionType: string;
|
|
28
34
|
};
|
|
29
35
|
/**
|
|
30
36
|
* Customizes how the plugin's rich text is authored and rendered. `converters` spread over the
|
|
31
37
|
* default Lexical node converters; `serialize` replaces the whole action-body pipeline (for
|
|
32
38
|
* non-HTML channels like chat or plain text, or to hand the body plus the submitted `form`/`req`
|
|
33
|
-
* off to a renderer like react-email). `editor` is the default Lexical/richText editor for every
|
|
39
|
+
* off to a renderer like react-email). Wrapping emails in a layout is `email.render`'s job. `editor` is the default Lexical/richText editor for every
|
|
34
40
|
* plugin-authored richText field: message content, consent statement, the response message, and
|
|
35
41
|
* the action body fields. `bodyEditor` overrides the action body fields specifically (emailTeam
|
|
36
42
|
* and confirmation), and `responseEditor` overrides the success `response` message field; both fall
|
|
@@ -64,7 +64,9 @@ const makeRenderBody = (args) => async (body) => {
|
|
|
64
64
|
values: args.values,
|
|
65
65
|
descriptors: args.descriptors,
|
|
66
66
|
form: args.form,
|
|
67
|
-
req: args.req
|
|
67
|
+
req: args.req,
|
|
68
|
+
locale: args.locale,
|
|
69
|
+
actionType: args.actionType
|
|
68
70
|
});
|
|
69
71
|
return serializeBody(body, {
|
|
70
72
|
values: args.values,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializeBody.js","names":[],"sources":["../../../src/actions/body/serializeBody.ts"],"sourcesContent":["import type { PayloadRequest, RichTextField } from 'payload'\nimport { interpolate } from '../../recall/interpolate'\nimport type { SubmissionDescriptor, SubmissionValue } from '../../submissions/types'\nimport type { BodyConverter, BodyRender } from './converters'\nimport { defaultBodyConverters } from './converters'\nimport { escapeHtml } from './escapeHtml'\nimport { serializeSlate } from './serializeSlate'\nimport { renderAllValues, renderAllValuesTable } from './wildcards'\n\n/** Minimal form identity threaded alongside a rendered body (e.g. per-tenant template lookups). */\ntype SerializeBodyForm = { id: number | string; title?: string }\n\n/** Submission data plus optional converter overrides available while serializing a body. */\nexport type BodyContext = {\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconverters?: Record<string, BodyConverter>\n}\n\n/**\n * Args a custom `richText.serialize` replacement receives per rendered body. Always populated by\n * `makeRenderBody` (the action-body pipeline): `form` and `req` enable per-tenant template\n * lookups or handing the body off to a renderer like react-email.\n */\nexport type SerializeBodyArgs = {\n\tbody: unknown\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tform: SerializeBodyForm\n\treq?: PayloadRequest\n}\n\n/**\n * Customizes how the plugin's rich text is authored and rendered. `converters` spread over the\n * default Lexical node converters; `serialize` replaces the whole action-body pipeline (for\n * non-HTML channels like chat or plain text, or to hand the body plus the submitted `form`/`req`\n * off to a renderer like react-email). `editor` is the default Lexical/richText editor for every\n * plugin-authored richText field: message content, consent statement, the response message, and\n * the action body fields. `bodyEditor` overrides the action body fields specifically (emailTeam\n * and confirmation), and `responseEditor` overrides the success `response` message field; both fall\n * back to `editor` when absent.\n */\nexport type RichTextBodyOption = {\n\tconverters?: Record<string, BodyConverter>\n\tserialize?: (args: SerializeBodyArgs) => Promise<string> | string\n\teditor?: RichTextField['editor']\n\tbodyEditor?: RichTextField['editor']\n\tresponseEditor?: RichTextField['editor']\n}\n\n/** Recall resolver over submission values: field name to stringified value, `''` when absent. */\nexport const resolverFor =\n\t(values: SubmissionValue[]) =>\n\t(name: string): string => {\n\t\tconst entry = values.find((value) => value.field === name)\n\t\treturn entry == null ? '' : String(entry.value ?? '')\n\t}\n\nconst renderFor = (ctx: BodyContext): BodyRender => {\n\tconst resolve = resolverFor(ctx.values)\n\tconst htmlResolve = (name: string): string => {\n\t\tif (name === '*') {\n\t\t\treturn renderAllValues(ctx.values, ctx.descriptors)\n\t\t}\n\t\tif (name === '*:table') {\n\t\t\treturn renderAllValuesTable(ctx.values, ctx.descriptors)\n\t\t}\n\t\treturn escapeHtml(resolve(name))\n\t}\n\treturn {\n\t\ttext: (raw) => interpolate(escapeHtml(raw), htmlResolve),\n\t\tinterpolate: (raw) => interpolate(raw, resolve),\n\t}\n}\n\nconst serializeNodes = (\n\tnodes: unknown[],\n\tconverters: Record<string, BodyConverter>,\n\trender: BodyRender\n): string =>\n\tnodes\n\t\t.map((node) => {\n\t\t\tif (node == null || typeof node !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst lexicalNode = node as Record<string, unknown>\n\t\t\tconst children = Array.isArray(lexicalNode.children)\n\t\t\t\t? serializeNodes(lexicalNode.children, converters, render)\n\t\t\t\t: ''\n\t\t\tconst converter =\n\t\t\t\ttypeof lexicalNode.type === 'string' ? converters[lexicalNode.type] : undefined\n\t\t\treturn converter ? converter({ node: lexicalNode, children, render }) : children\n\t\t})\n\t\t.join('')\n\nconst lexicalRootOf = (body: unknown): Record<string, unknown> | null => {\n\tif (body == null || typeof body !== 'object' || Array.isArray(body)) {\n\t\treturn null\n\t}\n\tconst root = (body as { root?: unknown }).root\n\treturn root != null && typeof root === 'object' ? (root as Record<string, unknown>) : null\n}\n\n/**\n * Serialize an action's `body` config into HTML. A legacy string body is interpolated as-is\n * (pre-richText behavior, no escaping); a Lexical state walks the converter registry; a Slate\n * array uses the minimal legacy serializer; anything else yields `''`. Rendered text is\n * HTML-escaped and supports `{{ name|fallback }}`, `{{*}}`, and `{{*:table}}` tokens.\n */\nexport const serializeBody = (body: unknown, ctx: BodyContext): string => {\n\tif (typeof body === 'string') {\n\t\treturn interpolate(body, resolverFor(ctx.values))\n\t}\n\tconst render = renderFor(ctx)\n\tif (Array.isArray(body)) {\n\t\treturn serializeSlate(body, render)\n\t}\n\tconst root = lexicalRootOf(body)\n\tif (root) {\n\t\tconst converters = { ...defaultBodyConverters, ...(ctx.converters ?? {}) }\n\t\treturn serializeNodes(Array.isArray(root.children) ? root.children : [], converters, render)\n\t}\n\treturn ''\n}\n\n/** Build the `renderBody` passed to actions, honoring a plugin-level `richText` customization. */\nexport const makeRenderBody =\n\t(args: {\n\t\tvalues: SubmissionValue[]\n\t\tdescriptors: SubmissionDescriptor[]\n\t\tform: SerializeBodyForm\n\t\treq?: PayloadRequest\n\t\trichText?: RichTextBodyOption\n\t}) =>\n\tasync (body: unknown): Promise<string> => {\n\t\tif (args.richText?.serialize) {\n\t\t\treturn await args.richText.serialize({\n\t\t\t\tbody,\n\t\t\t\tvalues: args.values,\n\t\t\t\tdescriptors: args.descriptors,\n\t\t\t\tform: args.form,\n\t\t\t\treq: args.req,\n\t\t\t})\n\t\t}\n\t\treturn serializeBody(body, {\n\t\t\tvalues: args.values,\n\t\t\tdescriptors: args.descriptors,\n\t\t\tconverters: args.richText?.converters,\n\t\t})\n\t}\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"serializeBody.js","names":[],"sources":["../../../src/actions/body/serializeBody.ts"],"sourcesContent":["import type { PayloadRequest, RichTextField } from 'payload'\nimport { interpolate } from '../../recall/interpolate'\nimport type { SubmissionDescriptor, SubmissionValue } from '../../submissions/types'\nimport type { BodyConverter, BodyRender } from './converters'\nimport { defaultBodyConverters } from './converters'\nimport { escapeHtml } from './escapeHtml'\nimport { serializeSlate } from './serializeSlate'\nimport { renderAllValues, renderAllValuesTable } from './wildcards'\n\n/** Minimal form identity threaded alongside a rendered body (e.g. per-tenant template lookups). */\ntype SerializeBodyForm = { id: number | string; title?: string }\n\n/** Submission data plus optional converter overrides available while serializing a body. */\nexport type BodyContext = {\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconverters?: Record<string, BodyConverter>\n}\n\n/**\n * Args a custom `richText.serialize` replacement receives per rendered body. Always populated by\n * `makeRenderBody` (the action-body pipeline): `form` and `req` enable per-tenant template\n * lookups or handing the body off to a renderer like react-email.\n */\nexport type SerializeBodyArgs = {\n\tbody: unknown\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tform: SerializeBodyForm\n\treq?: PayloadRequest\n\t/**\n\t * The submission's own stored locale, the one the form (and so `body`) was loaded at. Use it for\n\t * a wrapper's own strings rather than `req.locale`, which on the queued path is the job runner's.\n\t */\n\tlocale: string\n\t/** The `blockType` of the action rendering this body (e.g. `emailTeam`, `confirmation`). */\n\tactionType: string\n}\n\n/**\n * Customizes how the plugin's rich text is authored and rendered. `converters` spread over the\n * default Lexical node converters; `serialize` replaces the whole action-body pipeline (for\n * non-HTML channels like chat or plain text, or to hand the body plus the submitted `form`/`req`\n * off to a renderer like react-email). Wrapping emails in a layout is `email.render`'s job. `editor` is the default Lexical/richText editor for every\n * plugin-authored richText field: message content, consent statement, the response message, and\n * the action body fields. `bodyEditor` overrides the action body fields specifically (emailTeam\n * and confirmation), and `responseEditor` overrides the success `response` message field; both fall\n * back to `editor` when absent.\n */\nexport type RichTextBodyOption = {\n\tconverters?: Record<string, BodyConverter>\n\tserialize?: (args: SerializeBodyArgs) => Promise<string> | string\n\teditor?: RichTextField['editor']\n\tbodyEditor?: RichTextField['editor']\n\tresponseEditor?: RichTextField['editor']\n}\n\n/** Recall resolver over submission values: field name to stringified value, `''` when absent. */\nexport const resolverFor =\n\t(values: SubmissionValue[]) =>\n\t(name: string): string => {\n\t\tconst entry = values.find((value) => value.field === name)\n\t\treturn entry == null ? '' : String(entry.value ?? '')\n\t}\n\nconst renderFor = (ctx: BodyContext): BodyRender => {\n\tconst resolve = resolverFor(ctx.values)\n\tconst htmlResolve = (name: string): string => {\n\t\tif (name === '*') {\n\t\t\treturn renderAllValues(ctx.values, ctx.descriptors)\n\t\t}\n\t\tif (name === '*:table') {\n\t\t\treturn renderAllValuesTable(ctx.values, ctx.descriptors)\n\t\t}\n\t\treturn escapeHtml(resolve(name))\n\t}\n\treturn {\n\t\ttext: (raw) => interpolate(escapeHtml(raw), htmlResolve),\n\t\tinterpolate: (raw) => interpolate(raw, resolve),\n\t}\n}\n\nconst serializeNodes = (\n\tnodes: unknown[],\n\tconverters: Record<string, BodyConverter>,\n\trender: BodyRender\n): string =>\n\tnodes\n\t\t.map((node) => {\n\t\t\tif (node == null || typeof node !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst lexicalNode = node as Record<string, unknown>\n\t\t\tconst children = Array.isArray(lexicalNode.children)\n\t\t\t\t? serializeNodes(lexicalNode.children, converters, render)\n\t\t\t\t: ''\n\t\t\tconst converter =\n\t\t\t\ttypeof lexicalNode.type === 'string' ? converters[lexicalNode.type] : undefined\n\t\t\treturn converter ? converter({ node: lexicalNode, children, render }) : children\n\t\t})\n\t\t.join('')\n\nconst lexicalRootOf = (body: unknown): Record<string, unknown> | null => {\n\tif (body == null || typeof body !== 'object' || Array.isArray(body)) {\n\t\treturn null\n\t}\n\tconst root = (body as { root?: unknown }).root\n\treturn root != null && typeof root === 'object' ? (root as Record<string, unknown>) : null\n}\n\n/**\n * Serialize an action's `body` config into HTML. A legacy string body is interpolated as-is\n * (pre-richText behavior, no escaping); a Lexical state walks the converter registry; a Slate\n * array uses the minimal legacy serializer; anything else yields `''`. Rendered text is\n * HTML-escaped and supports `{{ name|fallback }}`, `{{*}}`, and `{{*:table}}` tokens.\n */\nexport const serializeBody = (body: unknown, ctx: BodyContext): string => {\n\tif (typeof body === 'string') {\n\t\treturn interpolate(body, resolverFor(ctx.values))\n\t}\n\tconst render = renderFor(ctx)\n\tif (Array.isArray(body)) {\n\t\treturn serializeSlate(body, render)\n\t}\n\tconst root = lexicalRootOf(body)\n\tif (root) {\n\t\tconst converters = { ...defaultBodyConverters, ...(ctx.converters ?? {}) }\n\t\treturn serializeNodes(Array.isArray(root.children) ? root.children : [], converters, render)\n\t}\n\treturn ''\n}\n\n/** Build the `renderBody` passed to actions, honoring a plugin-level `richText` customization. */\nexport const makeRenderBody =\n\t(args: {\n\t\tvalues: SubmissionValue[]\n\t\tdescriptors: SubmissionDescriptor[]\n\t\tform: SerializeBodyForm\n\t\treq?: PayloadRequest\n\t\tlocale: string\n\t\tactionType: string\n\t\trichText?: RichTextBodyOption\n\t}) =>\n\tasync (body: unknown): Promise<string> => {\n\t\tif (args.richText?.serialize) {\n\t\t\treturn await args.richText.serialize({\n\t\t\t\tbody,\n\t\t\t\tvalues: args.values,\n\t\t\t\tdescriptors: args.descriptors,\n\t\t\t\tform: args.form,\n\t\t\t\treq: args.req,\n\t\t\t\tlocale: args.locale,\n\t\t\t\tactionType: args.actionType,\n\t\t\t})\n\t\t}\n\t\treturn serializeBody(body, {\n\t\t\tvalues: args.values,\n\t\t\tdescriptors: args.descriptors,\n\t\t\tconverters: args.richText?.converters,\n\t\t})\n\t}\n"],"mappings":";;;;;;;AA0DA,MAAa,eACX,YACA,SAAyB;CACzB,MAAM,QAAQ,OAAO,MAAM,UAAU,MAAM,UAAU,IAAI;CACzD,OAAO,SAAS,OAAO,KAAK,OAAO,MAAM,SAAS,EAAE;AACrD;AAED,MAAM,aAAa,QAAiC;CACnD,MAAM,UAAU,YAAY,IAAI,MAAM;CACtC,MAAM,eAAe,SAAyB;EAC7C,IAAI,SAAS,KACZ,OAAO,gBAAgB,IAAI,QAAQ,IAAI,WAAW;EAEnD,IAAI,SAAS,WACZ,OAAO,qBAAqB,IAAI,QAAQ,IAAI,WAAW;EAExD,OAAO,WAAW,QAAQ,IAAI,CAAC;CAChC;CACA,OAAO;EACN,OAAO,QAAQ,YAAY,WAAW,GAAG,GAAG,WAAW;EACvD,cAAc,QAAQ,YAAY,KAAK,OAAO;CAC/C;AACD;AAEA,MAAM,kBACL,OACA,YACA,WAEA,MACE,KAAK,SAAS;CACd,IAAI,QAAQ,QAAQ,OAAO,SAAS,UACnC,OAAO;CAER,MAAM,cAAc;CACpB,MAAM,WAAW,MAAM,QAAQ,YAAY,QAAQ,IAChD,eAAe,YAAY,UAAU,YAAY,MAAM,IACvD;CACH,MAAM,YACL,OAAO,YAAY,SAAS,WAAW,WAAW,YAAY,QAAQ,KAAA;CACvE,OAAO,YAAY,UAAU;EAAE,MAAM;EAAa;EAAU;CAAO,CAAC,IAAI;AACzE,CAAC,EACA,KAAK,EAAE;AAEV,MAAM,iBAAiB,SAAkD;CACxE,IAAI,QAAQ,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACjE,OAAO;CAER,MAAM,OAAQ,KAA4B;CAC1C,OAAO,QAAQ,QAAQ,OAAO,SAAS,WAAY,OAAmC;AACvF;;;;;;;AAQA,MAAa,iBAAiB,MAAe,QAA6B;CACzE,IAAI,OAAO,SAAS,UACnB,OAAO,YAAY,MAAM,YAAY,IAAI,MAAM,CAAC;CAEjD,MAAM,SAAS,UAAU,GAAG;CAC5B,IAAI,MAAM,QAAQ,IAAI,GACrB,OAAO,eAAe,MAAM,MAAM;CAEnC,MAAM,OAAO,cAAc,IAAI;CAC/B,IAAI,MAAM;EACT,MAAM,aAAa;GAAE,GAAG;GAAuB,GAAI,IAAI,cAAc,CAAC;EAAG;EACzE,OAAO,eAAe,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC,GAAG,YAAY,MAAM;CAC5F;CACA,OAAO;AACR;;AAGA,MAAa,kBACX,SASD,OAAO,SAAmC;CACzC,IAAI,KAAK,UAAU,WAClB,OAAO,MAAM,KAAK,SAAS,UAAU;EACpC;EACA,QAAQ,KAAK;EACb,aAAa,KAAK;EAClB,MAAM,KAAK;EACX,KAAK,KAAK;EACV,QAAQ,KAAK;EACb,YAAY,KAAK;CAClB,CAAC;CAEF,OAAO,cAAc,MAAM;EAC1B,QAAQ,KAAK;EACb,aAAa,KAAK;EAClB,YAAY,KAAK,UAAU;CAC5B,CAAC;AACF"}
|
|
@@ -2,6 +2,7 @@ import { RecipientSourceRegistry } from "../recipientSources.js";
|
|
|
2
2
|
import { FromAddressSourceRegistry, FromAddressesResolver } from "../fromAddresses.js";
|
|
3
3
|
import { DepartmentEmailsResolver } from "../../email/departments.js";
|
|
4
4
|
import { RecipientsConfig } from "../emailRecipients.js";
|
|
5
|
+
import { EmailRender } from "../emailRender.js";
|
|
5
6
|
import { Field, RichTextField } from "payload";
|
|
6
7
|
|
|
7
8
|
//#region src/actions/builtin/emailAction.d.ts
|
|
@@ -13,7 +14,8 @@ type EmailActionOptions = {
|
|
|
13
14
|
fromSources?: FromAddressSourceRegistry;
|
|
14
15
|
departments?: DepartmentEmailsResolver;
|
|
15
16
|
recipients?: RecipientsConfig; /** Server-resolved recipient sources offered in every recipient list (plugin option `email.recipientSources`). */
|
|
16
|
-
recipientSources?: RecipientSourceRegistry;
|
|
17
|
+
recipientSources?: RecipientSourceRegistry; /** Produces the final html from the serialized body (plugin option `email.render`). */
|
|
18
|
+
render?: EmailRender;
|
|
17
19
|
};
|
|
18
20
|
//#endregion
|
|
19
21
|
export { EmailActionOptions };
|
|
@@ -13,11 +13,12 @@ import { sourcesByValue } from "../recipientSources.js";
|
|
|
13
13
|
* config (a first row pairing the action's target with `replyTo`, an optional `from` select, a
|
|
14
14
|
* cc/bcc row, a subject, and a rich text body, content and recipient fields carrying `localized`
|
|
15
15
|
* when `localize`) and an identical send (interpolate the subject, render the body, resolve
|
|
16
|
-
* cc/bcc/replyTo, and hand a single comma-joined
|
|
16
|
+
* cc/bcc/replyTo, pass the html through `options.render` when set, and hand a single comma-joined
|
|
17
|
+
* string per list to `payload.sendEmail`). Only the
|
|
17
18
|
* primary `to` target and its missing-value behavior differ, threaded through `spec`.
|
|
18
19
|
*/
|
|
19
20
|
const buildEmailAction = (options, spec) => {
|
|
20
|
-
const { localize, editor, fromAddresses, fromSources, departments, recipients, recipientSources } = options;
|
|
21
|
+
const { localize, editor, fromAddresses, fromSources, departments, recipients, recipientSources, render } = options;
|
|
21
22
|
const fromSourcesByValue = sourcesByValue(fromSources);
|
|
22
23
|
const endpoint = departments ? "departments" : void 0;
|
|
23
24
|
const recip = (name, labelKey) => buildRecipientField(name, labelKey, localize, {
|
|
@@ -81,7 +82,14 @@ const buildEmailAction = (options, spec) => {
|
|
|
81
82
|
}
|
|
82
83
|
if (typeof args.payload.sendEmail !== "function") throw new Error(`${spec.type}: no email adapter configured`);
|
|
83
84
|
const subject = interpolate(config.subject ?? "", resolve);
|
|
84
|
-
const
|
|
85
|
+
const serialized = await args.renderBody(config.body);
|
|
86
|
+
const html = render ? await render({
|
|
87
|
+
...sourceArgs,
|
|
88
|
+
html: serialized,
|
|
89
|
+
body: config.body,
|
|
90
|
+
subject,
|
|
91
|
+
actionType: spec.type
|
|
92
|
+
}) : serialized;
|
|
85
93
|
const cc = (await resolveRecipientEntries(config.cc, {
|
|
86
94
|
resolve,
|
|
87
95
|
sources,
|
|
@@ -1 +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 {\n\tbuildRecipientField,\n\ttype RecipientsConfig,\n\tresolveRecipientEntries,\n} from '../emailRecipients'\nimport {\n\tbuildFromField,\n\ttype FromAddressesResolver,\n\ttype FromAddressSourceRegistry,\n\tresolveSendFrom,\n} from '../fromAddresses'\nimport {\n\ttype RecipientResolveArgs,\n\ttype RecipientSource,\n\ttype RecipientSourceRegistry,\n\tsourcesByValue,\n} from '../recipientSources'\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\t/** Send-time-resolved senders offered in the from select (plugin option `email.fromSources`). */\n\tfromSources?: FromAddressSourceRegistry\n\tdepartments?: DepartmentEmailsResolver\n\trecipients?: RecipientsConfig\n\t/** Server-resolved recipient sources offered in every recipient list (plugin option `email.recipientSources`). */\n\trecipientSources?: RecipientSourceRegistry\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 `resolveTo` needs to compute the primary target, including server-resolved sources. */\ntype ResolveToArgs<TConfig extends EmailActionConfig> = {\n\tconfig: TConfig\n\tresolve: Resolver\n\tsources: Map<string, RecipientSource>\n\tsourceArgs: RecipientResolveArgs\n}\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` to a comma-joined address string, or `''` when nothing resolves. */\n\tresolveTo: (args: ResolveToArgs<TConfig>) => Promise<string> | string\n\t/**\n\t * Whether the author configured any target at all. A configured target that resolves empty (e.g. a\n\t * source returned `[]`) is a normal skip; only a target the author never set is a misconfiguration.\n\t */\n\thasTarget: (config: TConfig) => boolean\n\t/** With no target authored, `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 {\n\t\tlocalize,\n\t\teditor,\n\t\tfromAddresses,\n\t\tfromSources,\n\t\tdepartments,\n\t\trecipients,\n\t\trecipientSources,\n\t} = options\n\tconst fromSourcesByValue = sourcesByValue(fromSources)\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\tsources: recipientSources,\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 || fromSources ? [buildFromField(fromAddresses, fromSources)] : []),\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 (args) => {\n\t\t\tconst { config, values } = args\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst sources = sourcesByValue(recipientSources)\n\t\t\tconst sourceArgs: RecipientResolveArgs = {\n\t\t\t\tcontext: args.context,\n\t\t\t\tvalues,\n\t\t\t\tdescriptors: args.descriptors,\n\t\t\t\tform: args.form,\n\t\t\t\tsubmissionId: args.submissionId,\n\t\t\t\tpayload: args.payload,\n\t\t\t\treq: args.req,\n\t\t\t\tlocale: args.locale,\n\t\t\t}\n\t\t\tconst to = await spec.resolveTo({ config, resolve, sources, sourceArgs })\n\t\t\tif (!to) {\n\t\t\t\t// Nothing to send to. A target the author never configured is a misconfiguration (emailTeam\n\t\t\t\t// throws); a configured target that resolved empty (e.g. a source returned []) is a normal skip.\n\t\t\t\tif (!spec.hasTarget(config) && 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 args.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 args.renderBody(config.body)\n\t\t\tconst cc = (await resolveRecipientEntries(config.cc, { resolve, sources, sourceArgs })).join(\n\t\t\t\t', '\n\t\t\t)\n\t\t\tconst bcc = (\n\t\t\t\tawait resolveRecipientEntries(config.bcc, { resolve, sources, sourceArgs })\n\t\t\t).join(', ')\n\t\t\tconst replyTo = (\n\t\t\t\tawait resolveRecipientEntries(config.replyTo, { resolve, sources, sourceArgs })\n\t\t\t).join(', ')\n\n\t\t\t// A literal `from` was validated at save time against `fromAddresses(req)`; not re-checked\n\t\t\t// here (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 it is forwarded verbatim. A stored source\n\t\t\t// value instead resolves freshly on every send, so the sender follows the host (e.g. a\n\t\t\t// tenant that changed its address) rather than freezing at authoring time.\n\t\t\tconst from = await resolveSendFrom({\n\t\t\t\tconfigured: config.from,\n\t\t\t\tsources: fromSourcesByValue,\n\t\t\t\tsourceArgs,\n\t\t\t})\n\t\t\tawait args.payload.sendEmail({\n\t\t\t\tto,\n\t\t\t\tsubject,\n\t\t\t\thtml,\n\t\t\t\t...(from ? { 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":";;;;;;;;;;;;;;;;;;AAuFA,MAAa,oBACZ,SACA,SAC+B;CAC/B,MAAM,EACL,UACA,QACA,eACA,aACA,aACA,YACA,qBACG;CACJ,MAAM,qBAAqB,eAAe,WAAW;CACrD,MAAM,WAAW,cAAc,gBAAgB,KAAA;CAC/C,MAAM,SAAgC,MAAM,aAC3C,oBAAoB,MAAM,UAAU,UAAU;EAC7C;EACA;EACA,OAAO;EACP;EACA,SAAS;CACV,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,iBAAiB,cAAc,CAAC,eAAe,eAAe,WAAW,CAAC,IAAI,CAAC;GACnF;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,SAAS;GACpB,MAAM,EAAE,QAAQ,WAAW;GAC3B,MAAM,UAAU,YAAY,MAAM;GAClC,MAAM,UAAU,eAAe,gBAAgB;GAC/C,MAAM,aAAmC;IACxC,SAAS,KAAK;IACd;IACA,aAAa,KAAK;IAClB,MAAM,KAAK;IACX,cAAc,KAAK;IACnB,SAAS,KAAK;IACd,KAAK,KAAK;IACV,QAAQ,KAAK;GACd;GACA,MAAM,KAAK,MAAM,KAAK,UAAU;IAAE;IAAQ;IAAS;IAAS;GAAW,CAAC;GACxE,IAAI,CAAC,IAAI;IAGR,IAAI,CAAC,KAAK,UAAU,MAAM,KAAK,KAAK,gBAAgB,SACnD,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,uBAAuB;IAErD;GACD;GACA,IAAI,OAAO,KAAK,QAAQ,cAAc,YACrC,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,8BAA8B;GAG5D,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;GACzD,MAAM,OAAO,MAAM,KAAK,WAAW,OAAO,IAAI;GAC9C,MAAM,MAAM,MAAM,wBAAwB,OAAO,IAAI;IAAE;IAAS;IAAS;GAAW,CAAC,GAAG,KACvF,IACD;GACA,MAAM,OACL,MAAM,wBAAwB,OAAO,KAAK;IAAE;IAAS;IAAS;GAAW,CAAC,GACzE,KAAK,IAAI;GACX,MAAM,WACL,MAAM,wBAAwB,OAAO,SAAS;IAAE;IAAS;IAAS;GAAW,CAAC,GAC7E,KAAK,IAAI;GAOX,MAAM,OAAO,MAAM,gBAAgB;IAClC,YAAY,OAAO;IACnB,SAAS;IACT;GACD,CAAC;GACD,MAAM,KAAK,QAAQ,UAAU;IAC5B;IACA;IACA;IACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;IACvB,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
|
+
{"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 {\n\tbuildRecipientField,\n\ttype RecipientsConfig,\n\tresolveRecipientEntries,\n} from '../emailRecipients'\nimport type { EmailActionType, EmailRender } from '../emailRender'\nimport {\n\tbuildFromField,\n\ttype FromAddressesResolver,\n\ttype FromAddressSourceRegistry,\n\tresolveSendFrom,\n} from '../fromAddresses'\nimport {\n\ttype RecipientResolveArgs,\n\ttype RecipientSource,\n\ttype RecipientSourceRegistry,\n\tsourcesByValue,\n} from '../recipientSources'\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\t/** Send-time-resolved senders offered in the from select (plugin option `email.fromSources`). */\n\tfromSources?: FromAddressSourceRegistry\n\tdepartments?: DepartmentEmailsResolver\n\trecipients?: RecipientsConfig\n\t/** Server-resolved recipient sources offered in every recipient list (plugin option `email.recipientSources`). */\n\trecipientSources?: RecipientSourceRegistry\n\t/** Produces the final html from the serialized body (plugin option `email.render`). */\n\trender?: EmailRender\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 `resolveTo` needs to compute the primary target, including server-resolved sources. */\ntype ResolveToArgs<TConfig extends EmailActionConfig> = {\n\tconfig: TConfig\n\tresolve: Resolver\n\tsources: Map<string, RecipientSource>\n\tsourceArgs: RecipientResolveArgs\n}\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: EmailActionType\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` to a comma-joined address string, or `''` when nothing resolves. */\n\tresolveTo: (args: ResolveToArgs<TConfig>) => Promise<string> | string\n\t/**\n\t * Whether the author configured any target at all. A configured target that resolves empty (e.g. a\n\t * source returned `[]`) is a normal skip; only a target the author never set is a misconfiguration.\n\t */\n\thasTarget: (config: TConfig) => boolean\n\t/** With no target authored, `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, pass the html through `options.render` when set, and hand a single comma-joined\n * 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 {\n\t\tlocalize,\n\t\teditor,\n\t\tfromAddresses,\n\t\tfromSources,\n\t\tdepartments,\n\t\trecipients,\n\t\trecipientSources,\n\t\trender,\n\t} = options\n\tconst fromSourcesByValue = sourcesByValue(fromSources)\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\tsources: recipientSources,\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 || fromSources ? [buildFromField(fromAddresses, fromSources)] : []),\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 (args) => {\n\t\t\tconst { config, values } = args\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst sources = sourcesByValue(recipientSources)\n\t\t\tconst sourceArgs: RecipientResolveArgs = {\n\t\t\t\tcontext: args.context,\n\t\t\t\tvalues,\n\t\t\t\tdescriptors: args.descriptors,\n\t\t\t\tform: args.form,\n\t\t\t\tsubmissionId: args.submissionId,\n\t\t\t\tpayload: args.payload,\n\t\t\t\treq: args.req,\n\t\t\t\tlocale: args.locale,\n\t\t\t}\n\t\t\tconst to = await spec.resolveTo({ config, resolve, sources, sourceArgs })\n\t\t\tif (!to) {\n\t\t\t\t// Nothing to send to. A target the author never configured is a misconfiguration (emailTeam\n\t\t\t\t// throws); a configured target that resolved empty (e.g. a source returned []) is a normal skip.\n\t\t\t\tif (!spec.hasTarget(config) && 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 args.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 serialized = await args.renderBody(config.body)\n\t\t\tconst html = render\n\t\t\t\t? await render({\n\t\t\t\t\t\t...sourceArgs,\n\t\t\t\t\t\thtml: serialized,\n\t\t\t\t\t\tbody: config.body,\n\t\t\t\t\t\tsubject,\n\t\t\t\t\t\tactionType: spec.type,\n\t\t\t\t\t})\n\t\t\t\t: serialized\n\t\t\tconst cc = (await resolveRecipientEntries(config.cc, { resolve, sources, sourceArgs })).join(\n\t\t\t\t', '\n\t\t\t)\n\t\t\tconst bcc = (\n\t\t\t\tawait resolveRecipientEntries(config.bcc, { resolve, sources, sourceArgs })\n\t\t\t).join(', ')\n\t\t\tconst replyTo = (\n\t\t\t\tawait resolveRecipientEntries(config.replyTo, { resolve, sources, sourceArgs })\n\t\t\t).join(', ')\n\n\t\t\t// A literal `from` was validated at save time against `fromAddresses(req)`; not re-checked\n\t\t\t// here (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 it is forwarded verbatim. A stored source\n\t\t\t// value instead resolves freshly on every send, so the sender follows the host (e.g. a\n\t\t\t// tenant that changed its address) rather than freezing at authoring time.\n\t\t\tconst from = await resolveSendFrom({\n\t\t\t\tconfigured: config.from,\n\t\t\t\tsources: fromSourcesByValue,\n\t\t\t\tsourceArgs,\n\t\t\t})\n\t\t\tawait args.payload.sendEmail({\n\t\t\t\tto,\n\t\t\t\tsubject,\n\t\t\t\thtml,\n\t\t\t\t...(from ? { 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":";;;;;;;;;;;;;;;;;;;AA2FA,MAAa,oBACZ,SACA,SAC+B;CAC/B,MAAM,EACL,UACA,QACA,eACA,aACA,aACA,YACA,kBACA,WACG;CACJ,MAAM,qBAAqB,eAAe,WAAW;CACrD,MAAM,WAAW,cAAc,gBAAgB,KAAA;CAC/C,MAAM,SAAgC,MAAM,aAC3C,oBAAoB,MAAM,UAAU,UAAU;EAC7C;EACA;EACA,OAAO;EACP;EACA,SAAS;CACV,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,iBAAiB,cAAc,CAAC,eAAe,eAAe,WAAW,CAAC,IAAI,CAAC;GACnF;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,SAAS;GACpB,MAAM,EAAE,QAAQ,WAAW;GAC3B,MAAM,UAAU,YAAY,MAAM;GAClC,MAAM,UAAU,eAAe,gBAAgB;GAC/C,MAAM,aAAmC;IACxC,SAAS,KAAK;IACd;IACA,aAAa,KAAK;IAClB,MAAM,KAAK;IACX,cAAc,KAAK;IACnB,SAAS,KAAK;IACd,KAAK,KAAK;IACV,QAAQ,KAAK;GACd;GACA,MAAM,KAAK,MAAM,KAAK,UAAU;IAAE;IAAQ;IAAS;IAAS;GAAW,CAAC;GACxE,IAAI,CAAC,IAAI;IAGR,IAAI,CAAC,KAAK,UAAU,MAAM,KAAK,KAAK,gBAAgB,SACnD,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,uBAAuB;IAErD;GACD;GACA,IAAI,OAAO,KAAK,QAAQ,cAAc,YACrC,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK,8BAA8B;GAG5D,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;GACzD,MAAM,aAAa,MAAM,KAAK,WAAW,OAAO,IAAI;GACpD,MAAM,OAAO,SACV,MAAM,OAAO;IACb,GAAG;IACH,MAAM;IACN,MAAM,OAAO;IACb;IACA,YAAY,KAAK;GAClB,CAAC,IACA;GACH,MAAM,MAAM,MAAM,wBAAwB,OAAO,IAAI;IAAE;IAAS;IAAS;GAAW,CAAC,GAAG,KACvF,IACD;GACA,MAAM,OACL,MAAM,wBAAwB,OAAO,KAAK;IAAE;IAAS;IAAS;GAAW,CAAC,GACzE,KAAK,IAAI;GACX,MAAM,WACL,MAAM,wBAAwB,OAAO,SAAS;IAAE;IAAS;IAAS;GAAW,CAAC,GAC7E,KAAK,IAAI;GAOX,MAAM,OAAO,MAAM,gBAAgB;IAClC,YAAY,OAAO;IACnB,SAAS;IACT;GACD,CAAC;GACD,MAAM,KAAK,QAAQ,UAAU;IAC5B;IACA;IACA;IACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;IACvB,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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RecipientResolveArgs } from "./recipientSources.js";
|
|
2
|
+
|
|
3
|
+
//#region src/actions/emailRender.d.ts
|
|
4
|
+
/** The built-in email actions `email.render` runs for. */
|
|
5
|
+
type EmailActionType = 'emailTeam' | 'confirmation';
|
|
6
|
+
/**
|
|
7
|
+
* What `email.render` receives per outgoing email: the finished body `html` (the default pipeline's
|
|
8
|
+
* output, or `richText.serialize`'s when set), the raw `body` it was rendered from, the interpolated
|
|
9
|
+
* `subject`, the `actionType` sending it, and the same run-time args a recipient source gets:
|
|
10
|
+
* `locale` (the submission's own), `form`, `submissionId`, `values`, `descriptors`, `context`,
|
|
11
|
+
* `payload`, and `req`.
|
|
12
|
+
*/
|
|
13
|
+
type EmailRenderArgs = RecipientResolveArgs & {
|
|
14
|
+
html: string; /** The action's stored rich text (or legacy string) body config, before serialization. */
|
|
15
|
+
body: unknown;
|
|
16
|
+
subject: string;
|
|
17
|
+
actionType: EmailActionType;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Host hook producing the final `html` of every built-in email (plugin option `email.render`), e.g.
|
|
21
|
+
* to wrap the body in a branded, localized layout. It runs after the body is serialized, so it only
|
|
22
|
+
* wraps and never has to re-render rich text. A throw fails the action like a send failure does.
|
|
23
|
+
*/
|
|
24
|
+
type EmailRender = (args: EmailRenderArgs) => Promise<string> | string;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { EmailActionType, EmailRender, EmailRenderArgs };
|
|
27
|
+
//# sourceMappingURL=emailRender.d.ts.map
|
|
@@ -7,17 +7,19 @@ const detailOf = (error) => error != null && typeof error === "object" && "detai
|
|
|
7
7
|
*/
|
|
8
8
|
const runActions = async (args) => {
|
|
9
9
|
const { actions, registry, richText, ...ctx } = args;
|
|
10
|
-
const renderBody = makeRenderBody({
|
|
11
|
-
values: ctx.values,
|
|
12
|
-
descriptors: ctx.descriptors,
|
|
13
|
-
form: ctx.form,
|
|
14
|
-
req: ctx.req,
|
|
15
|
-
richText
|
|
16
|
-
});
|
|
17
10
|
const results = [];
|
|
18
11
|
for (const instance of actions) {
|
|
19
12
|
const definition = registry.get(instance.blockType);
|
|
20
13
|
if (!definition) continue;
|
|
14
|
+
const renderBody = makeRenderBody({
|
|
15
|
+
values: ctx.values,
|
|
16
|
+
descriptors: ctx.descriptors,
|
|
17
|
+
form: ctx.form,
|
|
18
|
+
req: ctx.req,
|
|
19
|
+
locale: ctx.locale,
|
|
20
|
+
actionType: instance.blockType,
|
|
21
|
+
richText
|
|
22
|
+
});
|
|
21
23
|
try {
|
|
22
24
|
await definition.run({
|
|
23
25
|
...ctx,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { RichTextBodyOption } from './body/serializeBody'\nimport { makeRenderBody } from './body/serializeBody'\nimport type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\n/** `detail` carries whatever structured context the action attached to its throw (see `ActionError`). */\nexport type ActionResult = { type: string; ok: boolean; error?: string; detail?: unknown }\n\nconst detailOf = (error: unknown): unknown =>\n\terror != null && typeof error === 'object' && 'detail' in error\n\t\t? (error as { detail?: unknown }).detail\n\t\t: undefined\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config' | 'renderBody'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n\trichText?: RichTextBodyOption\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, richText, ...ctx } = args\n\tconst renderBody = makeRenderBody({\n\t\tvalues: ctx.values,\n\t\tdescriptors: ctx.descriptors,\n\t\tform: ctx.form,\n\t\treq: ctx.req,\n\t\
|
|
1
|
+
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { RichTextBodyOption } from './body/serializeBody'\nimport { makeRenderBody } from './body/serializeBody'\nimport type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\n/** `detail` carries whatever structured context the action attached to its throw (see `ActionError`). */\nexport type ActionResult = { type: string; ok: boolean; error?: string; detail?: unknown }\n\nconst detailOf = (error: unknown): unknown =>\n\terror != null && typeof error === 'object' && 'detail' in error\n\t\t? (error as { detail?: unknown }).detail\n\t\t: undefined\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config' | 'renderBody'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n\trichText?: RichTextBodyOption\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, richText, ...ctx } = args\n\tconst results: ActionResult[] = []\n\tfor (const instance of actions) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\tconst renderBody = makeRenderBody({\n\t\t\tvalues: ctx.values,\n\t\t\tdescriptors: ctx.descriptors,\n\t\t\tform: ctx.form,\n\t\t\treq: ctx.req,\n\t\t\tlocale: ctx.locale,\n\t\t\tactionType: instance.blockType,\n\t\t\trichText,\n\t\t})\n\t\ttry {\n\t\t\tawait definition.run({ ...ctx, renderBody, config: instance })\n\t\t\tresults.push({ type: instance.blockType, ok: true })\n\t\t} catch (error) {\n\t\t\tconst detail = detailOf(error)\n\t\t\tresults.push({\n\t\t\t\ttype: instance.blockType,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t...(detail !== undefined ? { detail } : {}),\n\t\t\t})\n\t\t}\n\t}\n\treturn results\n}\n"],"mappings":";;AAWA,MAAM,YAAY,UACjB,SAAS,QAAQ,OAAO,UAAU,YAAY,YAAY,QACtD,MAA+B,SAChC,KAAA;;;;;AAYJ,MAAa,aAAa,OAAO,SAAkD;CAClF,MAAM,EAAE,SAAS,UAAU,UAAU,GAAG,QAAQ;CAChD,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,YAAY,SAAS;EAC/B,MAAM,aAAa,SAAS,IAAI,SAAS,SAAS;EAClD,IAAI,CAAC,YACJ;EAED,MAAM,aAAa,eAAe;GACjC,QAAQ,IAAI;GACZ,aAAa,IAAI;GACjB,MAAM,IAAI;GACV,KAAK,IAAI;GACT,QAAQ,IAAI;GACZ,YAAY,SAAS;GACrB;EACD,CAAC;EACD,IAAI;GACH,MAAM,WAAW,IAAI;IAAE,GAAG;IAAK;IAAY,QAAQ;GAAS,CAAC;GAC7D,QAAQ,KAAK;IAAE,MAAM,SAAS;IAAW,IAAI;GAAK,CAAC;EACpD,SAAS,OAAO;GACf,MAAM,SAAS,SAAS,KAAK;GAC7B,QAAQ,KAAK;IACZ,MAAM,SAAS;IACf,IAAI;IACJ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC5D,GAAI,WAAW,KAAA,IAAY,EAAE,OAAO,IAAI,CAAC;GAC1C,CAAC;EACF;CACD;CACA,OAAO;AACR"}
|
|
@@ -7,7 +7,8 @@ import { isLoggedIn } from "../plugin/access.js";
|
|
|
7
7
|
import { FORMS_SLUG } from "./forms.js";
|
|
8
8
|
import { formIdOf } from "../submissions/formIdOf.js";
|
|
9
9
|
import { makeVoteTallyHook } from "../poll/votes/voteTallyHook.js";
|
|
10
|
-
import {
|
|
10
|
+
import { voteChangeTargetOf } from "../submissions/voteChange.js";
|
|
11
|
+
import { POLL_CONTEXT_KEY, VOTED_COOKIE_MAX_AGE_SECONDS, signVotedCookieValue, votedCookieName } from "../submissions/votedCookie.js";
|
|
11
12
|
import { buildSpamGuard } from "../spam/spamGuard.js";
|
|
12
13
|
import { validateSubmission } from "../submissions/validateSubmission.js";
|
|
13
14
|
import { verifyContext } from "../submissions/verifyContext.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formSubmissions.js","names":[],"sources":["../../src/collections/formSubmissions.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, Field, PayloadRequest } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { dispatchActions } from '../actions/dispatch'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { CalcFunction, CalcSource } from '../calc/registry'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport { resolveEventSink } from '../events/resolveEventSink'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { pollConfigOf } from '../form/pollState'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { makeVoteTallyHook } from '../poll/votes/voteTallyHook'\nimport { buildSpamGuard } from '../spam/spamGuard'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { formIdOf } from '../submissions/formIdOf'\nimport { validateSubmission } from '../submissions/validateSubmission'\nimport { verifyContext } from '../submissions/verifyContext'\nimport { buildVoteSubmitEndpoint } from '../submissions/voteChangeEndpoint'\nimport {\n\tPOLL_CONTEXT_KEY,\n\ttype PollContextState,\n\tsignVotedCookieValue,\n\tVOTED_COOKIE_MAX_AGE_SECONDS,\n\tvoteChangeTargetOf,\n\tvotedCookieName,\n} from '../submissions/votedCookie'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { FORMS_SLUG } from './forms'\n\nexport const FORM_SUBMISSIONS_SLUG = 'form-submissions'\n\ntype BuildSubmissionsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc sources; submission validation re-resolves them fresh (a failure rejects the submission). */\n\tcalcSources?: Record<string, CalcSource>\n\t/** Registered calc functions; threaded into submit-time calc evaluation. */\n\tcalcFunctions?: Record<string, CalcFunction>\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry?: ActionRegistry\n\tevents?: FormEventSink\n\t/** Whether a job runner is likely present; gates the queued vs bounded-inline dispatch path. */\n\thasRunner?: boolean\n\t/** Host override for the inline dispatch deadline (plugin option `dispatch.deadlineMs`). */\n\tdispatchDeadlineMs?: number\n\t/** Body serialization customization forwarded to the inline action dispatch path. */\n\trichText?: RichTextBodyOption\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Resolved spam config; when active, prepends the spam guard before validation. `false` disables it. */\n\tspam?: ResolvedSpamConfig | false\n\t/**\n\t * Opt-in (`poll.votedCookie`): set an httpOnly `fb-voted-{formId}` cookie on poll submission\n\t * creates. An `allowChange` poll sets its signed-id cookie regardless of this flag.\n\t */\n\tvotedCookie?: boolean\n\t/** Registered poll option sources; submission validation resolves allowed values through them. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Resolved `poll.votes` option; `false` skips registering the append-only vote tally hook. */\n\tpollVotes?: false | { overrides?: CollectionOverrides }\n\t/**\n\t * When `true`, reveals the standalone `locale`, `values`, `descriptors`, `consent`, and `meta`\n\t * fields in the admin UI. Default `false`, because the `SubmissionAnswers` UI component already\n\t * represents them fully (locale and meta are folded into its Submission-details section).\n\t */\n\tshowRawFields?: boolean\n\toverrides?: CollectionOverrides\n}\n\n/**\n * On a completed submission create, dispatch the form's post-submit actions and emit `submission.created`.\n * Both are side effects on an already-written row, so the whole body is wrapped: nothing it does can throw\n * past the hook (a failed action or sink must never fail the submission write). Dispatch is itself bounded\n * and non-throwing; the inline fallback is awaited so the bound caps the added latency, while the queued\n * path returns immediately. The form's `actions` are null-guarded for legacy rows created before the field\n * existed.\n */\nconst makeAfterChange =\n\t(args: {\n\t\tactionRegistry: ActionRegistry\n\t\tevents?: FormEventSink\n\t\thasRunner: boolean\n\t\trichText?: RichTextBodyOption\n\t\tdispatchDeadlineMs?: number\n\t}): CollectionAfterChangeHook =>\n\tasync ({ doc, operation, req }) => {\n\t\tif (operation !== 'create' || (doc.status != null && doc.status !== 'complete')) {\n\t\t\treturn doc\n\t\t}\n\t\tconst { payload } = req\n\t\ttry {\n\t\t\tconst formId = formIdOf(doc.form)\n\t\t\tif (formId == null) {\n\t\t\t\treturn doc\n\t\t\t}\n\t\t\tconst form = await payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\n\t\t\t// Field access blocks every API writer, so this slug-agnostic override write is the only\n\t\t\t// path that can set the outcome stamps. `withReq` only while the create transaction is\n\t\t\t// still open; a late-settlement write happens after the response and passes none.\n\t\t\tconst stamp = async (\n\t\t\t\tdata: { actionFailed?: boolean; actionUncertain?: boolean },\n\t\t\t\twithReq?: PayloadRequest\n\t\t\t) => {\n\t\t\t\tconst update = payload.update.bind(payload) as unknown as (options: {\n\t\t\t\t\tcollection: string\n\t\t\t\t\tid: number | string\n\t\t\t\t\tdata: { actionFailed?: boolean; actionUncertain?: boolean }\n\t\t\t\t\tdepth?: number\n\t\t\t\t\toverrideAccess?: boolean\n\t\t\t\t\treq?: PayloadRequest\n\t\t\t\t}) => Promise<unknown>\n\t\t\t\tawait update({\n\t\t\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\t\t\tid: doc.id as number | string,\n\t\t\t\t\tdata,\n\t\t\t\t\tdepth: 0,\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\t...(withReq ? { req: withReq } : {}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst emitCreated = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait resolveEventSink(args.events).emit({\n\t\t\t\t\t\ttype: 'submission.created',\n\t\t\t\t\t\tformId: String(formId),\n\t\t\t\t\t\tsubmissionId: String(doc.id),\n\t\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tpayload.logger?.error(\n\t\t\t\t\t\t`@10x-media/form-builder: submission.created sink threw: ${\n\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t\t}`\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst { essentialFailed } = await dispatchActions({\n\t\t\t\tactions: (form?.actions ?? null) as ActionInstance[] | null,\n\t\t\t\tformId,\n\t\t\t\tsubmissionId: doc.id as number | string,\n\t\t\t\tregistry: args.actionRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\thasRunner: args.hasRunner,\n\t\t\t\tpersistSubmissions: form?.persistSubmissions as boolean | undefined,\n\t\t\t\trichText: args.richText,\n\t\t\t\tdeadlineMs: args.dispatchDeadlineMs,\n\t\t\t\t// A definite refusal stamps `actionFailed` (the operator's replay filter); a deadline\n\t\t\t\t// breach stamps `actionUncertain` instead, because the work may still land and a\n\t\t\t\t// premature failure stamp would invite a duplicate replay.\n\t\t\t\tonEssentialFailed: ({ timedOut }) =>\n\t\t\t\t\tstamp(timedOut ? { actionUncertain: true } : { actionFailed: true }, req),\n\t\t\t\t// The real outcome of a breached pass, recorded durably once the work settles. The\n\t\t\t\t// dispatcher invokes this AFTER the late closing pass on success, so the stamp only\n\t\t\t\t// clears once the skipped work (rest actions, prune) is done and the created event\n\t\t\t\t// follows it, mirroring the on-time order; a `persistSubmissions: false` prune has\n\t\t\t\t// already deleted the row by then, so the clear tolerates a missing target. A late\n\t\t\t\t// failure converges to the definite-failure stamp.\n\t\t\t\tonEssentialSettled: async ({ ok }) => {\n\t\t\t\t\tif (ok) {\n\t\t\t\t\t\tawait stamp({ actionUncertain: false }).catch(() => {})\n\t\t\t\t\t\tawait emitCreated()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait stamp({ actionFailed: true, actionUncertain: false })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t})\n\t\t\t// A failed-essential submission is kept for recovery but is not a completed signup: no\n\t\t\t// created event, so a sink-driven automation never treats it as one.\n\t\t\tif (essentialFailed) {\n\t\t\t\treturn doc\n\t\t\t}\n\n\t\t\tawait emitCreated()\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: afterChange dispatch failed for submission ${String(doc.id)}: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t)\n\t\t}\n\t\treturn doc\n\t}\n\nconst isPollContextState = (value: unknown): value is PollContextState =>\n\tvalue != null &&\n\ttypeof value === 'object' &&\n\ttypeof (value as PollContextState).pollEnabled === 'boolean'\n\n/**\n * Voted-cookie hook: after a submission to a poll-enabled form is created (or updated through the\n * vote-change path), appends an httpOnly `fb-voted-{formId}` cookie via `req.responseHeaders`,\n * Payload's supported channel for hook-set response headers (its own auth strategies use it;\n * `handleEndpoints` merges it into every REST response). The value stays the legacy `1` marker\n * under the `poll.votedCookie` plugin option; an `allowChange` poll instead carries the signed\n * submission id (set regardless of the option, because re-vote identification depends on it) and\n * refreshes it on every change. Reads the poll state `validateSubmission` stashed on\n * `req.context`, falling back to a form fetch when absent. httpOnly keeps the marker readable\n * only server-side (`hasVotedCookie`/`resolveVotedSubmission`); the client `<Poll>` keeps its\n * localStorage guard.\n */\nconst makeVotedCookieHook = (args: { votedCookie: boolean }): CollectionAfterChangeHook => {\n\treturn async ({ doc, operation, req }) => {\n\t\tconst changing = operation === 'update' && voteChangeTargetOf(req) != null\n\t\tif (operation !== 'create' && !changing) {\n\t\t\treturn doc\n\t\t}\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) {\n\t\t\treturn doc\n\t\t}\n\t\tconst stashed = req.context?.[POLL_CONTEXT_KEY]\n\t\tlet state = isPollContextState(stashed) ? stashed : undefined\n\t\tif (state === undefined) {\n\t\t\tconst form = await req.payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\t\t\tstate = {\n\t\t\t\tpollEnabled: form?.pollEnabled === true,\n\t\t\t\tallowChange: pollConfigOf(form?.poll)?.allowChange === true,\n\t\t\t}\n\t\t}\n\t\tif (!state.pollEnabled || !(args.votedCookie || state.allowChange)) {\n\t\t\treturn doc\n\t\t}\n\t\tconst value = state.allowChange\n\t\t\t? signVotedCookieValue(req.payload, doc.id as number | string)\n\t\t\t: '1'\n\t\t// A signed id is a bearer capability (it authorizes changing that vote), so it follows the\n\t\t// host's Payload auth-cookie transport policy: `Secure` whenever the admin user collection's\n\t\t// `auth.cookies.secure` is on. The legacy `1` marker stays a plain UX flag either way.\n\t\tconst adminUserSlug = req.payload.config.admin?.user\n\t\t// String-indexed cast: a host's generated types key `collections` to its own slugs, which\n\t\t// this framework-level read cannot know.\n\t\tconst collections = req.payload.collections as Record<\n\t\t\tstring,\n\t\t\t{ config: { auth?: { cookies?: { secure?: boolean } } } } | undefined\n\t\t>\n\t\tconst authCookies = adminUserSlug ? collections[adminUserSlug]?.config.auth?.cookies : undefined\n\t\tconst secure = state.allowChange && authCookies?.secure === true ? '; Secure' : ''\n\t\treq.responseHeaders ??= new Headers()\n\t\treq.responseHeaders.append(\n\t\t\t'Set-Cookie',\n\t\t\t`${votedCookieName(formId)}=${value}; Path=/; Max-Age=${VOTED_COOKIE_MAX_AGE_SECONDS}; HttpOnly; SameSite=Lax${secure}`\n\t\t)\n\t\treturn doc\n\t}\n}\n\nexport const buildSubmissionsCollection = ({\n\tregistry,\n\truleRegistry,\n\tcalcSources,\n\tcalcFunctions,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry = new Map(),\n\tevents,\n\thasRunner = false,\n\tdispatchDeadlineMs,\n\trichText,\n\tuploadSlug,\n\tspam,\n\tvotedCookie = false,\n\tpollSourceRegistry,\n\tpollVotes = false,\n\tshowRawFields = false,\n\toverrides,\n}: BuildSubmissionsCollectionArgs): CollectionConfig => {\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'form', type: 'relationship', relationTo: FORMS_SLUG, required: true },\n\t\t{\n\t\t\tname: 'status',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: 'complete',\n\t\t\toptions: [\n\t\t\t\t{ label: labelForKey(keys.statusComplete), value: 'complete' },\n\t\t\t\t{ label: labelForKey(keys.statusPartial), value: 'partial' },\n\t\t\t],\n\t\t\t// Not clearable: aggregation counts `status: complete` submissions, so a cleared status\n\t\t\t// would drop the submission out of every poll result with nothing in the UI saying so.\n\t\t\tadmin: { isClearable: false },\n\t\t\t// Defense-in-depth at the REST layer: anonymous clients cannot set status via the API.\n\t\t\t// The validateSubmission hook also forces 'complete' server-side, so this covers both paths.\n\t\t\taccess: { create: isLoggedIn, update: isLoggedIn },\n\t\t},\n\t\t// Stamped by the dispatcher when an essential action failed and the row was kept: the flag is\n\t\t// what lets an operator filter these ownerless rows and clear them once the provider has been\n\t\t// fixed and the addresses replayed. Hidden while unset; never client-writable.\n\t\t{\n\t\t\tname: 'actionFailed',\n\t\t\ttype: 'checkbox',\n\t\t\tindex: true,\n\t\t\tlabel: labelForKey(keys.submissionActionFailedFlag),\n\t\t\taccess: { create: () => false, update: () => false },\n\t\t\tadmin: { readOnly: true, condition: (data) => data?.actionFailed === true },\n\t\t},\n\t\t// Stamped instead of `actionFailed` when the essential pass outlived its deadline: the work\n\t\t// may still complete. Cleared once the breached work settles successfully (the row leaves\n\t\t// both filters), or upgraded to `actionFailed` when it settles as a definite failure.\n\t\t{\n\t\t\tname: 'actionUncertain',\n\t\t\ttype: 'checkbox',\n\t\t\tindex: true,\n\t\t\tlabel: labelForKey(keys.submissionActionUncertainFlag),\n\t\t\taccess: { create: () => false, update: () => false },\n\t\t\tadmin: { readOnly: true, condition: (data) => data?.actionUncertain === true },\n\t\t},\n\t\t// answers UI appears first so it is the dominant view when opening a submission document.\n\t\t{\n\t\t\tname: 'answers',\n\t\t\ttype: 'ui',\n\t\t\tadmin: {\n\t\t\t\tcomponents: { Field: '@10x-media/form-builder/rsc#SubmissionAnswers' },\n\t\t\t},\n\t\t},\n\t\t{ name: 'locale', type: 'text', admin: { hidden: !showRawFields } },\n\t\t{ name: 'values', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'descriptors', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'consent', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'meta', type: 'json', admin: { hidden: !showRawFields } },\n\t\t// The verified reference to the document this form was rendered for (see verifyContext). Only forms\n\t\t// rendered with a signed context carry one, so the group is shown only when present. Readable, so it\n\t\t// is a legible audit record of which document a submission came through.\n\t\t{\n\t\t\tname: 'context',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.submissionContext),\n\t\t\t// A Payload group always materializes as an object, so gate on a populated `relationTo`\n\t\t\t// (not the group itself) to show it only for submissions made through a signed context.\n\t\t\tadmin: { readOnly: true, condition: (data) => Boolean(data?.context?.relationTo) },\n\t\t\tfields: [\n\t\t\t\t{ name: 'relationTo', type: 'text' },\n\t\t\t\t{ name: 'value', type: 'text' },\n\t\t\t],\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORM_SUBMISSIONS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionSubmissionSingular),\n\t\t\tplural: labelForKey(keys.collectionSubmissionPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tdefaultColumns: ['form', 'status', 'locale', 'createdAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: {\n\t\t\tcreate: () => true,\n\t\t\tread: isLoggedIn,\n\t\t\tupdate: () => false,\n\t\t\t...(overrides?.access ?? {}),\n\t\t},\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// Spam guard + validateSubmission must remain first: they enforce the security invariant\n\t\t\t// that anonymous callers cannot bypass post-submit actions or supply a forged status.\n\t\t\t// Consumer beforeValidate hooks are appended after so they run on already-validated data.\n\t\t\tbeforeValidate: [\n\t\t\t\t...(spam ? [buildSpamGuard(spam)] : []),\n\t\t\t\t// Verify + store the signed form context before validation stores the answers, and independently\n\t\t\t\t// of spam so it runs even with spam off.\n\t\t\t\tverifyContext(),\n\t\t\t\tvalidateSubmission({\n\t\t\t\t\tregistry,\n\t\t\t\t\truleRegistry,\n\t\t\t\t\tcalcSources,\n\t\t\t\t\tcalcFunctions,\n\t\t\t\t\tconsentSources,\n\t\t\t\t\tconsentSnapshot,\n\t\t\t\t\tuploadSlug,\n\t\t\t\t\tpollSourceRegistry,\n\t\t\t\t\tstrictUploadOwnership: spam ? spam.uploadOwnership === 'strict' : false,\n\t\t\t\t}),\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tafterChange: [\n\t\t\t\t// The tally hook runs first and does not swallow errors: a bump failure must throw so\n\t\t\t\t// Payload rolls the submission create/update back inside the operation transaction,\n\t\t\t\t// rather than being absorbed by the dispatch hook's swallow-all error boundary below.\n\t\t\t\t...(pollVotes !== false ? [makeVoteTallyHook()] : []),\n\t\t\t\tmakeAfterChange({ actionRegistry, events, hasRunner, richText, dispatchDeadlineMs }),\n\t\t\t\t// Always registered: it self-gates on the option and on the form's allowChange flag.\n\t\t\t\tmakeVotedCookieHook({ votedCookie }),\n\t\t\t\t...(overrides?.hooks?.afterChange ?? []),\n\t\t\t],\n\t\t},\n\t\t// The vote-submit endpoint must stay first so it shadows the stock REST create (custom\n\t\t// endpoints match before built-ins); `endpoints: false` from a host disables ours too.\n\t\tendpoints:\n\t\t\toverrides?.endpoints === false\n\t\t\t\t? false\n\t\t\t\t: [buildVoteSubmitEndpoint(), ...(overrides?.endpoints ?? [])],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"formSubmissions.js","names":[],"sources":["../../src/collections/formSubmissions.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, Field, PayloadRequest } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { dispatchActions } from '../actions/dispatch'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { CalcFunction, CalcSource } from '../calc/registry'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport { resolveEventSink } from '../events/resolveEventSink'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { pollConfigOf } from '../form/pollState'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { makeVoteTallyHook } from '../poll/votes/voteTallyHook'\nimport { buildSpamGuard } from '../spam/spamGuard'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { formIdOf } from '../submissions/formIdOf'\nimport { validateSubmission } from '../submissions/validateSubmission'\nimport { verifyContext } from '../submissions/verifyContext'\nimport { buildVoteSubmitEndpoint } from '../submissions/voteChangeEndpoint'\nimport {\n\tPOLL_CONTEXT_KEY,\n\ttype PollContextState,\n\tsignVotedCookieValue,\n\tVOTED_COOKIE_MAX_AGE_SECONDS,\n\tvoteChangeTargetOf,\n\tvotedCookieName,\n} from '../submissions/votedCookie'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { FORMS_SLUG } from './forms'\n\nexport const FORM_SUBMISSIONS_SLUG = 'form-submissions'\n\ntype BuildSubmissionsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc sources; submission validation re-resolves them fresh (a failure rejects the submission). */\n\tcalcSources?: Record<string, CalcSource>\n\t/** Registered calc functions; threaded into submit-time calc evaluation. */\n\tcalcFunctions?: Record<string, CalcFunction>\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry?: ActionRegistry\n\tevents?: FormEventSink\n\t/** Whether a job runner is likely present; gates the queued vs bounded-inline dispatch path. */\n\thasRunner?: boolean\n\t/** Host override for the inline dispatch deadline (plugin option `dispatch.deadlineMs`). */\n\tdispatchDeadlineMs?: number\n\t/** Body serialization customization forwarded to the inline action dispatch path. */\n\trichText?: RichTextBodyOption\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Resolved spam config; when active, prepends the spam guard before validation. `false` disables it. */\n\tspam?: ResolvedSpamConfig | false\n\t/**\n\t * Opt-in (`poll.votedCookie`): set an httpOnly `fb-voted-{formId}` cookie on poll submission\n\t * creates. An `allowChange` poll sets its signed-id cookie regardless of this flag.\n\t */\n\tvotedCookie?: boolean\n\t/** Registered poll option sources; submission validation resolves allowed values through them. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Resolved `poll.votes` option; `false` skips registering the append-only vote tally hook. */\n\tpollVotes?: false | { overrides?: CollectionOverrides }\n\t/**\n\t * When `true`, reveals the standalone `locale`, `values`, `descriptors`, `consent`, and `meta`\n\t * fields in the admin UI. Default `false`, because the `SubmissionAnswers` UI component already\n\t * represents them fully (locale and meta are folded into its Submission-details section).\n\t */\n\tshowRawFields?: boolean\n\toverrides?: CollectionOverrides\n}\n\n/**\n * On a completed submission create, dispatch the form's post-submit actions and emit `submission.created`.\n * Both are side effects on an already-written row, so the whole body is wrapped: nothing it does can throw\n * past the hook (a failed action or sink must never fail the submission write). Dispatch is itself bounded\n * and non-throwing; the inline fallback is awaited so the bound caps the added latency, while the queued\n * path returns immediately. The form's `actions` are null-guarded for legacy rows created before the field\n * existed.\n */\nconst makeAfterChange =\n\t(args: {\n\t\tactionRegistry: ActionRegistry\n\t\tevents?: FormEventSink\n\t\thasRunner: boolean\n\t\trichText?: RichTextBodyOption\n\t\tdispatchDeadlineMs?: number\n\t}): CollectionAfterChangeHook =>\n\tasync ({ doc, operation, req }) => {\n\t\tif (operation !== 'create' || (doc.status != null && doc.status !== 'complete')) {\n\t\t\treturn doc\n\t\t}\n\t\tconst { payload } = req\n\t\ttry {\n\t\t\tconst formId = formIdOf(doc.form)\n\t\t\tif (formId == null) {\n\t\t\t\treturn doc\n\t\t\t}\n\t\t\tconst form = await payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\n\t\t\t// Field access blocks every API writer, so this slug-agnostic override write is the only\n\t\t\t// path that can set the outcome stamps. `withReq` only while the create transaction is\n\t\t\t// still open; a late-settlement write happens after the response and passes none.\n\t\t\tconst stamp = async (\n\t\t\t\tdata: { actionFailed?: boolean; actionUncertain?: boolean },\n\t\t\t\twithReq?: PayloadRequest\n\t\t\t) => {\n\t\t\t\tconst update = payload.update.bind(payload) as unknown as (options: {\n\t\t\t\t\tcollection: string\n\t\t\t\t\tid: number | string\n\t\t\t\t\tdata: { actionFailed?: boolean; actionUncertain?: boolean }\n\t\t\t\t\tdepth?: number\n\t\t\t\t\toverrideAccess?: boolean\n\t\t\t\t\treq?: PayloadRequest\n\t\t\t\t}) => Promise<unknown>\n\t\t\t\tawait update({\n\t\t\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\t\t\tid: doc.id as number | string,\n\t\t\t\t\tdata,\n\t\t\t\t\tdepth: 0,\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\t...(withReq ? { req: withReq } : {}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst emitCreated = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait resolveEventSink(args.events).emit({\n\t\t\t\t\t\ttype: 'submission.created',\n\t\t\t\t\t\tformId: String(formId),\n\t\t\t\t\t\tsubmissionId: String(doc.id),\n\t\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tpayload.logger?.error(\n\t\t\t\t\t\t`@10x-media/form-builder: submission.created sink threw: ${\n\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t\t}`\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst { essentialFailed } = await dispatchActions({\n\t\t\t\tactions: (form?.actions ?? null) as ActionInstance[] | null,\n\t\t\t\tformId,\n\t\t\t\tsubmissionId: doc.id as number | string,\n\t\t\t\tregistry: args.actionRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\thasRunner: args.hasRunner,\n\t\t\t\tpersistSubmissions: form?.persistSubmissions as boolean | undefined,\n\t\t\t\trichText: args.richText,\n\t\t\t\tdeadlineMs: args.dispatchDeadlineMs,\n\t\t\t\t// A definite refusal stamps `actionFailed` (the operator's replay filter); a deadline\n\t\t\t\t// breach stamps `actionUncertain` instead, because the work may still land and a\n\t\t\t\t// premature failure stamp would invite a duplicate replay.\n\t\t\t\tonEssentialFailed: ({ timedOut }) =>\n\t\t\t\t\tstamp(timedOut ? { actionUncertain: true } : { actionFailed: true }, req),\n\t\t\t\t// The real outcome of a breached pass, recorded durably once the work settles. The\n\t\t\t\t// dispatcher invokes this AFTER the late closing pass on success, so the stamp only\n\t\t\t\t// clears once the skipped work (rest actions, prune) is done and the created event\n\t\t\t\t// follows it, mirroring the on-time order; a `persistSubmissions: false` prune has\n\t\t\t\t// already deleted the row by then, so the clear tolerates a missing target. A late\n\t\t\t\t// failure converges to the definite-failure stamp.\n\t\t\t\tonEssentialSettled: async ({ ok }) => {\n\t\t\t\t\tif (ok) {\n\t\t\t\t\t\tawait stamp({ actionUncertain: false }).catch(() => {})\n\t\t\t\t\t\tawait emitCreated()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait stamp({ actionFailed: true, actionUncertain: false })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t})\n\t\t\t// A failed-essential submission is kept for recovery but is not a completed signup: no\n\t\t\t// created event, so a sink-driven automation never treats it as one.\n\t\t\tif (essentialFailed) {\n\t\t\t\treturn doc\n\t\t\t}\n\n\t\t\tawait emitCreated()\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: afterChange dispatch failed for submission ${String(doc.id)}: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t)\n\t\t}\n\t\treturn doc\n\t}\n\nconst isPollContextState = (value: unknown): value is PollContextState =>\n\tvalue != null &&\n\ttypeof value === 'object' &&\n\ttypeof (value as PollContextState).pollEnabled === 'boolean'\n\n/**\n * Voted-cookie hook: after a submission to a poll-enabled form is created (or updated through the\n * vote-change path), appends an httpOnly `fb-voted-{formId}` cookie via `req.responseHeaders`,\n * Payload's supported channel for hook-set response headers (its own auth strategies use it;\n * `handleEndpoints` merges it into every REST response). The value stays the legacy `1` marker\n * under the `poll.votedCookie` plugin option; an `allowChange` poll instead carries the signed\n * submission id (set regardless of the option, because re-vote identification depends on it) and\n * refreshes it on every change. Reads the poll state `validateSubmission` stashed on\n * `req.context`, falling back to a form fetch when absent. httpOnly keeps the marker readable\n * only server-side (`hasVotedCookie`/`resolveVotedSubmission`); the client `<Poll>` keeps its\n * localStorage guard.\n */\nconst makeVotedCookieHook = (args: { votedCookie: boolean }): CollectionAfterChangeHook => {\n\treturn async ({ doc, operation, req }) => {\n\t\tconst changing = operation === 'update' && voteChangeTargetOf(req) != null\n\t\tif (operation !== 'create' && !changing) {\n\t\t\treturn doc\n\t\t}\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) {\n\t\t\treturn doc\n\t\t}\n\t\tconst stashed = req.context?.[POLL_CONTEXT_KEY]\n\t\tlet state = isPollContextState(stashed) ? stashed : undefined\n\t\tif (state === undefined) {\n\t\t\tconst form = await req.payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\t\t\tstate = {\n\t\t\t\tpollEnabled: form?.pollEnabled === true,\n\t\t\t\tallowChange: pollConfigOf(form?.poll)?.allowChange === true,\n\t\t\t}\n\t\t}\n\t\tif (!state.pollEnabled || !(args.votedCookie || state.allowChange)) {\n\t\t\treturn doc\n\t\t}\n\t\tconst value = state.allowChange\n\t\t\t? signVotedCookieValue(req.payload, doc.id as number | string)\n\t\t\t: '1'\n\t\t// A signed id is a bearer capability (it authorizes changing that vote), so it follows the\n\t\t// host's Payload auth-cookie transport policy: `Secure` whenever the admin user collection's\n\t\t// `auth.cookies.secure` is on. The legacy `1` marker stays a plain UX flag either way.\n\t\tconst adminUserSlug = req.payload.config.admin?.user\n\t\t// String-indexed cast: a host's generated types key `collections` to its own slugs, which\n\t\t// this framework-level read cannot know.\n\t\tconst collections = req.payload.collections as Record<\n\t\t\tstring,\n\t\t\t{ config: { auth?: { cookies?: { secure?: boolean } } } } | undefined\n\t\t>\n\t\tconst authCookies = adminUserSlug ? collections[adminUserSlug]?.config.auth?.cookies : undefined\n\t\tconst secure = state.allowChange && authCookies?.secure === true ? '; Secure' : ''\n\t\treq.responseHeaders ??= new Headers()\n\t\treq.responseHeaders.append(\n\t\t\t'Set-Cookie',\n\t\t\t`${votedCookieName(formId)}=${value}; Path=/; Max-Age=${VOTED_COOKIE_MAX_AGE_SECONDS}; HttpOnly; SameSite=Lax${secure}`\n\t\t)\n\t\treturn doc\n\t}\n}\n\nexport const buildSubmissionsCollection = ({\n\tregistry,\n\truleRegistry,\n\tcalcSources,\n\tcalcFunctions,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry = new Map(),\n\tevents,\n\thasRunner = false,\n\tdispatchDeadlineMs,\n\trichText,\n\tuploadSlug,\n\tspam,\n\tvotedCookie = false,\n\tpollSourceRegistry,\n\tpollVotes = false,\n\tshowRawFields = false,\n\toverrides,\n}: BuildSubmissionsCollectionArgs): CollectionConfig => {\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'form', type: 'relationship', relationTo: FORMS_SLUG, required: true },\n\t\t{\n\t\t\tname: 'status',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: 'complete',\n\t\t\toptions: [\n\t\t\t\t{ label: labelForKey(keys.statusComplete), value: 'complete' },\n\t\t\t\t{ label: labelForKey(keys.statusPartial), value: 'partial' },\n\t\t\t],\n\t\t\t// Not clearable: aggregation counts `status: complete` submissions, so a cleared status\n\t\t\t// would drop the submission out of every poll result with nothing in the UI saying so.\n\t\t\tadmin: { isClearable: false },\n\t\t\t// Defense-in-depth at the REST layer: anonymous clients cannot set status via the API.\n\t\t\t// The validateSubmission hook also forces 'complete' server-side, so this covers both paths.\n\t\t\taccess: { create: isLoggedIn, update: isLoggedIn },\n\t\t},\n\t\t// Stamped by the dispatcher when an essential action failed and the row was kept: the flag is\n\t\t// what lets an operator filter these ownerless rows and clear them once the provider has been\n\t\t// fixed and the addresses replayed. Hidden while unset; never client-writable.\n\t\t{\n\t\t\tname: 'actionFailed',\n\t\t\ttype: 'checkbox',\n\t\t\tindex: true,\n\t\t\tlabel: labelForKey(keys.submissionActionFailedFlag),\n\t\t\taccess: { create: () => false, update: () => false },\n\t\t\tadmin: { readOnly: true, condition: (data) => data?.actionFailed === true },\n\t\t},\n\t\t// Stamped instead of `actionFailed` when the essential pass outlived its deadline: the work\n\t\t// may still complete. Cleared once the breached work settles successfully (the row leaves\n\t\t// both filters), or upgraded to `actionFailed` when it settles as a definite failure.\n\t\t{\n\t\t\tname: 'actionUncertain',\n\t\t\ttype: 'checkbox',\n\t\t\tindex: true,\n\t\t\tlabel: labelForKey(keys.submissionActionUncertainFlag),\n\t\t\taccess: { create: () => false, update: () => false },\n\t\t\tadmin: { readOnly: true, condition: (data) => data?.actionUncertain === true },\n\t\t},\n\t\t// answers UI appears first so it is the dominant view when opening a submission document.\n\t\t{\n\t\t\tname: 'answers',\n\t\t\ttype: 'ui',\n\t\t\tadmin: {\n\t\t\t\tcomponents: { Field: '@10x-media/form-builder/rsc#SubmissionAnswers' },\n\t\t\t},\n\t\t},\n\t\t{ name: 'locale', type: 'text', admin: { hidden: !showRawFields } },\n\t\t{ name: 'values', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'descriptors', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'consent', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'meta', type: 'json', admin: { hidden: !showRawFields } },\n\t\t// The verified reference to the document this form was rendered for (see verifyContext). Only forms\n\t\t// rendered with a signed context carry one, so the group is shown only when present. Readable, so it\n\t\t// is a legible audit record of which document a submission came through.\n\t\t{\n\t\t\tname: 'context',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.submissionContext),\n\t\t\t// A Payload group always materializes as an object, so gate on a populated `relationTo`\n\t\t\t// (not the group itself) to show it only for submissions made through a signed context.\n\t\t\tadmin: { readOnly: true, condition: (data) => Boolean(data?.context?.relationTo) },\n\t\t\tfields: [\n\t\t\t\t{ name: 'relationTo', type: 'text' },\n\t\t\t\t{ name: 'value', type: 'text' },\n\t\t\t],\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORM_SUBMISSIONS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionSubmissionSingular),\n\t\t\tplural: labelForKey(keys.collectionSubmissionPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tdefaultColumns: ['form', 'status', 'locale', 'createdAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: {\n\t\t\tcreate: () => true,\n\t\t\tread: isLoggedIn,\n\t\t\tupdate: () => false,\n\t\t\t...(overrides?.access ?? {}),\n\t\t},\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// Spam guard + validateSubmission must remain first: they enforce the security invariant\n\t\t\t// that anonymous callers cannot bypass post-submit actions or supply a forged status.\n\t\t\t// Consumer beforeValidate hooks are appended after so they run on already-validated data.\n\t\t\tbeforeValidate: [\n\t\t\t\t...(spam ? [buildSpamGuard(spam)] : []),\n\t\t\t\t// Verify + store the signed form context before validation stores the answers, and independently\n\t\t\t\t// of spam so it runs even with spam off.\n\t\t\t\tverifyContext(),\n\t\t\t\tvalidateSubmission({\n\t\t\t\t\tregistry,\n\t\t\t\t\truleRegistry,\n\t\t\t\t\tcalcSources,\n\t\t\t\t\tcalcFunctions,\n\t\t\t\t\tconsentSources,\n\t\t\t\t\tconsentSnapshot,\n\t\t\t\t\tuploadSlug,\n\t\t\t\t\tpollSourceRegistry,\n\t\t\t\t\tstrictUploadOwnership: spam ? spam.uploadOwnership === 'strict' : false,\n\t\t\t\t}),\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tafterChange: [\n\t\t\t\t// The tally hook runs first and does not swallow errors: a bump failure must throw so\n\t\t\t\t// Payload rolls the submission create/update back inside the operation transaction,\n\t\t\t\t// rather than being absorbed by the dispatch hook's swallow-all error boundary below.\n\t\t\t\t...(pollVotes !== false ? [makeVoteTallyHook()] : []),\n\t\t\t\tmakeAfterChange({ actionRegistry, events, hasRunner, richText, dispatchDeadlineMs }),\n\t\t\t\t// Always registered: it self-gates on the option and on the form's allowChange flag.\n\t\t\t\tmakeVotedCookieHook({ votedCookie }),\n\t\t\t\t...(overrides?.hooks?.afterChange ?? []),\n\t\t\t],\n\t\t},\n\t\t// The vote-submit endpoint must stay first so it shadows the stock REST create (custom\n\t\t// endpoints match before built-ins); `endpoints: false` from a host disables ours too.\n\t\tendpoints:\n\t\t\toverrides?.endpoints === false\n\t\t\t\t? false\n\t\t\t\t: [buildVoteSubmitEndpoint(), ...(overrides?.endpoints ?? [])],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { ResolveConsentEntriesArgs, resolveConsentEntries } from "./consent/reso
|
|
|
48
48
|
import { ConsentSourceOption, ResolveConsentSourcesRequestArgs, ResolveConsentSourcesRequestResult, resolveConsentSourcesRequest } from "./consent/resolveConsentSourcesRequest.js";
|
|
49
49
|
import { ResolvePublishedVersionRefArgs, resolvePublishedVersionRef } from "./consent/resolvePublishedVersionRef.js";
|
|
50
50
|
import { DepartmentEmailsResolver, DepartmentOption, ResolveDepartmentOptionsArgs, ResolveDepartmentsRequestArgs, ResolveDepartmentsRequestResult, resolveDepartmentOptions } from "./email/departments.js";
|
|
51
|
+
import { EmailActionType, EmailRender, EmailRenderArgs } from "./actions/emailRender.js";
|
|
51
52
|
import { CalcFunction, CalcSource, CalcSourceResolveArgs, CalcWeightResolveArgs, defineCalcFunction, defineCalcSource } from "./calc/registry.js";
|
|
52
53
|
import { RedirectFieldsOverride, RedirectOption, ResponseOption } from "./collections/redirectFields.js";
|
|
53
54
|
import { DefaultSettingsFields, SettingsFieldsOverride, SettingsOption, buildDefaultSettingsFields } from "./collections/settingsFields.js";
|
|
@@ -96,5 +97,5 @@ import { FieldTargetParamOptions, fieldTargetParam } from "./validation/fieldTar
|
|
|
96
97
|
//#region src/index.d.ts
|
|
97
98
|
declare const formBuilder: (options: FormBuilderPluginOptions) => import("payload").Plugin;
|
|
98
99
|
//#endregion
|
|
99
|
-
export { type ActionDefinition, ActionError, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionValidateArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressSource, type FromAddressSourceRegistry, type FromAddressesResolver, type HcaptchaProviderOptions, INLINE_DISPATCH_DEADLINE_MS, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, type VotedSubmission, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
|
|
100
|
+
export { type ActionDefinition, ActionError, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionValidateArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type EmailActionType, type EmailRender, type EmailRenderArgs, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressSource, type FromAddressSourceRegistry, type FromAddressesResolver, type HcaptchaProviderOptions, INLINE_DISPATCH_DEADLINE_MS, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, type VotedSubmission, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
|
|
100
101
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,8 @@ const formBuilder = definePlugin({
|
|
|
117
117
|
fromSources,
|
|
118
118
|
departments,
|
|
119
119
|
recipients: options.email?.recipients,
|
|
120
|
-
recipientSources: options.email?.recipientSources
|
|
120
|
+
recipientSources: options.email?.recipientSources,
|
|
121
|
+
render: options.email?.render
|
|
121
122
|
}), options.actions);
|
|
122
123
|
assertNoActionBlockCollision(config, actionRegistry);
|
|
123
124
|
const spam = resolveSpamConfig(options.spam);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport type { FormBuilderPluginOptions } from './options'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst fromSources = options.email?.fromSources\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tfromSources,\n\t\t\t\tdepartments,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tdispatchDeadlineMs: options.dispatch?.deadlineMs,\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type {\n\tActionDefinition,\n\tActionRunArgs,\n\tActionValidateArgs,\n\tAnyActionDefinition,\n} from './actions/defineAction'\nexport { ActionError, defineAction } from './actions/defineAction'\nexport { INLINE_DISPATCH_DEADLINE_MS } from './actions/dispatch'\nexport type {\n\tFromAddressesResolver,\n\tFromAddressOption,\n\tFromAddressSource,\n\tFromAddressSourceRegistry,\n} from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport type { VotedSubmission } from './submissions/resolveVotedSubmission'\nexport { resolveVotedSubmission } from './submissions/resolveVotedSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,kBAAkB,QAAQ,oBAAoB;EACpD,MAAM,UAAU,QAAQ,WAAW;EACnC,MAAM,cAAc,QAAQ,MAAM,WAAW,CAAC;EAC9C,MAAM,gBAAgB,QAAQ,MAAM,aAAa,CAAC;EAGlD,8BAA8B,aAAa;EAC3C,0BAA0B,WAAW;EAIrC,MAAM,cAAc;GACnB,SAAS,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;GACzC,WAAW,IAAI,IAAI,OAAO,KAAK,aAAa,CAAC;EAC9C;EAGA,MAAM,iBAAiB,OAAO,QAAQ,WAAW,EAAE,KAAK,CAAC,KAAK,aAAa;GAC1E;GACA,OAAO,OAAO;GACd,QAAQ,OAAO,OAAO,YAAY;GAClC,SAAS,OAAO,OAAO,mBAAmB;EAC3C,EAAE;EAIF,MAAM,kBACL,YAAY,QACT,KAAA,IACC,QAAQ,aAAa,8BAA8B,QAAQ,QAAQ,UAAU;EAClF,MAAM,iBAAiB,QAAQ,SAAS;EAgBxC,MAAM,WAAW,kBAXe,6BAC/B,iBACA,QAAQ,UAAU,QAClB,iBACA,aACA,cACD,EAAE,QACA,gBACC,YAAY,SAAS,WAAW,SAAS,YACzC,mBAAmB,KAAA,KAAa,WAAW,SAAS,UAEE,GAAG,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,gBAAgB,QAAQ,OAAO;EACrC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,iBAAiB,eACtB,8BAA8B;GAC7B,UAAU;GACV,QAAQ,QAAQ,UAAU,cAAc,QAAQ,UAAU;GAC1D;GACA;GACA;GACA,YAAY,QAAQ,OAAO;GAC3B,kBAAkB,QAAQ,OAAO;EAClC,CAAC,GACD,QAAQ,OACT;EAGA,6BAA6B,QAAQ,cAAc;EACnD,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,MAAM,qBAAqB,yBAAyB,QAAQ,MAAM,OAAO;EACzE,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,KAAK;EAM7D,OAAO,SAAS,uBAAuB,OAAO,QAAQ,kBAAkB;EACxE,OAAO,SAAS,eAAe,OAAO,QAAQ,gBAAgB;EAC9D,OAAO,SAAS,gBAAgB,OAAO,QAAQ,QAAQ;EACvD,IAAI,gBACH,OAAO,SAAS,oBAAoB,OAAO,QAAQ,cAAc;EAElE,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,iBAAiB,QAAQ,SAAS,YAAY;GAC9C,sBAAsB,QAAQ,SAAS;GACvC;GACA,UAAU,QAAQ;GAClB,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,oBAAoB,QAAQ,UAAU;GACtC,QAAQ,QAAQ;GAChB;GACA;GACA,yBAAyB,QAAQ,2BAA2B;GAC5D;GACA,eAAe,QAAQ,SAAS;GAChC,aAAa,QAAQ,MAAM,gBAAgB;GAC3C;GACA;GACA,eAAe,QAAQ,MAAM;GAC7B,WAAW,QAAQ,MAAM,UAAU,QAAQ,QAAS,QAAQ,MAAM,SAAS,CAAC;GAC5E,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB;GACA;GACA;GACA,uBAAuB,QAAQ;GAC/B,WAAW,QAAQ;EACpB,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport type { FormBuilderPluginOptions } from './options'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst fromSources = options.email?.fromSources\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tfromSources,\n\t\t\t\tdepartments,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t\trender: options.email?.render,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tdispatchDeadlineMs: options.dispatch?.deadlineMs,\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type {\n\tActionDefinition,\n\tActionRunArgs,\n\tActionValidateArgs,\n\tAnyActionDefinition,\n} from './actions/defineAction'\nexport { ActionError, defineAction } from './actions/defineAction'\nexport { INLINE_DISPATCH_DEADLINE_MS } from './actions/dispatch'\nexport type { EmailActionType, EmailRender, EmailRenderArgs } from './actions/emailRender'\nexport type {\n\tFromAddressesResolver,\n\tFromAddressOption,\n\tFromAddressSource,\n\tFromAddressSourceRegistry,\n} from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport type { VotedSubmission } from './submissions/resolveVotedSubmission'\nexport { resolveVotedSubmission } from './submissions/resolveVotedSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,kBAAkB,QAAQ,oBAAoB;EACpD,MAAM,UAAU,QAAQ,WAAW;EACnC,MAAM,cAAc,QAAQ,MAAM,WAAW,CAAC;EAC9C,MAAM,gBAAgB,QAAQ,MAAM,aAAa,CAAC;EAGlD,8BAA8B,aAAa;EAC3C,0BAA0B,WAAW;EAIrC,MAAM,cAAc;GACnB,SAAS,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;GACzC,WAAW,IAAI,IAAI,OAAO,KAAK,aAAa,CAAC;EAC9C;EAGA,MAAM,iBAAiB,OAAO,QAAQ,WAAW,EAAE,KAAK,CAAC,KAAK,aAAa;GAC1E;GACA,OAAO,OAAO;GACd,QAAQ,OAAO,OAAO,YAAY;GAClC,SAAS,OAAO,OAAO,mBAAmB;EAC3C,EAAE;EAIF,MAAM,kBACL,YAAY,QACT,KAAA,IACC,QAAQ,aAAa,8BAA8B,QAAQ,QAAQ,UAAU;EAClF,MAAM,iBAAiB,QAAQ,SAAS;EAgBxC,MAAM,WAAW,kBAXe,6BAC/B,iBACA,QAAQ,UAAU,QAClB,iBACA,aACA,cACD,EAAE,QACA,gBACC,YAAY,SAAS,WAAW,SAAS,YACzC,mBAAmB,KAAA,KAAa,WAAW,SAAS,UAEE,GAAG,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,gBAAgB,QAAQ,OAAO;EACrC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,iBAAiB,eACtB,8BAA8B;GAC7B,UAAU;GACV,QAAQ,QAAQ,UAAU,cAAc,QAAQ,UAAU;GAC1D;GACA;GACA;GACA,YAAY,QAAQ,OAAO;GAC3B,kBAAkB,QAAQ,OAAO;GACjC,QAAQ,QAAQ,OAAO;EACxB,CAAC,GACD,QAAQ,OACT;EAGA,6BAA6B,QAAQ,cAAc;EACnD,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,MAAM,qBAAqB,yBAAyB,QAAQ,MAAM,OAAO;EACzE,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,KAAK;EAM7D,OAAO,SAAS,uBAAuB,OAAO,QAAQ,kBAAkB;EACxE,OAAO,SAAS,eAAe,OAAO,QAAQ,gBAAgB;EAC9D,OAAO,SAAS,gBAAgB,OAAO,QAAQ,QAAQ;EACvD,IAAI,gBACH,OAAO,SAAS,oBAAoB,OAAO,QAAQ,cAAc;EAElE,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,iBAAiB,QAAQ,SAAS,YAAY;GAC9C,sBAAsB,QAAQ,SAAS;GACvC;GACA,UAAU,QAAQ;GAClB,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,oBAAoB,QAAQ,UAAU;GACtC,QAAQ,QAAQ;GAChB;GACA;GACA,yBAAyB,QAAQ,2BAA2B;GAC5D;GACA,eAAe,QAAQ,SAAS;GAChC,aAAa,QAAQ,MAAM,gBAAgB;GAC3C;GACA;GACA,eAAe,QAAQ,MAAM;GAC7B,WAAW,QAAQ,MAAM,UAAU,QAAQ,QAAS,QAAQ,MAAM,SAAS,CAAC;GAC5E,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB;GACA;GACA;GACA,uBAAuB,QAAQ;GAC/B,WAAW,QAAQ;EACpB,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
|