@getquick/content 0.5.1 → 0.6.1
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/README.md +1 -1
- package/dist/{client-ip-cnqPLdDa.js → client-ip-NKt34dBM.js} +8 -3
- package/dist/consent-html.js +1 -1
- package/dist/consent-server.js +1 -1
- package/dist/consent.js +1 -1
- package/dist/{copy-DLto_bWj.js → copy-CfNr6M73.js} +22 -5
- package/dist/{descriptor-B-e7gHi1.d.ts → descriptor-BF5Y9a1J.d.ts} +33 -3
- package/dist/{descriptor-x8kHFQ79.js → descriptor-DXmLmjEa.js} +67 -3
- package/dist/{design-fonts-tbvMfxXU.js → design-fonts-IaTVX3bG.js} +18 -3
- package/dist/forms-astro.d.ts +4 -2
- package/dist/forms-astro.js +40 -7
- package/dist/forms-server.d.ts +13 -2
- package/dist/forms-server.js +72 -18
- package/dist/forms-testing.d.ts +60 -4
- package/dist/forms-testing.js +317 -8
- package/dist/forms.d.ts +41 -3
- package/dist/forms.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/{state-CsMFLUUO.d.ts → state-Bw-HSBeO.d.ts} +12 -2
- package/dist/{validate-wRgYUNxN.js → validate-CqP_F0bk.js} +110 -2
- package/dist/{version-7v3zP5aF.js → version-BZolkooG.js} +1 -1
- package/dist/{wordpress-D6eSi2Ff.d.ts → wordpress-CZ4JCdXF.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,7 +311,7 @@ regenerating both stylesheets, including its media thresholds.
|
|
|
311
311
|
|
|
312
312
|
### Fonts and text styles
|
|
313
313
|
|
|
314
|
-
GQ Design 0.10.1 or newer (contract 2.1.0; this package bundles 0.10.
|
|
314
|
+
GQ Design 0.10.1 or newer (contract 2.1.0; this package bundles 0.10.4) delivers the Site's self-hosted font families
|
|
315
315
|
(faces on the Site's own or media host, each with a metric-matched local
|
|
316
316
|
fallback), the `sans`/`serif`/`mono` slot assignments and the Heading and
|
|
317
317
|
Paragraph text styles. The design presets carry them as optional `fonts` and
|
|
@@ -19,8 +19,13 @@ function isSameOrigin(request, origins) {
|
|
|
19
19
|
}
|
|
20
20
|
/** The body as text, or null once it exceeds `limit` bytes (declared or read). */
|
|
21
21
|
async function readBoundedBody(request, limit) {
|
|
22
|
+
const bytes = await readBoundedBytes(request, limit);
|
|
23
|
+
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
24
|
+
}
|
|
25
|
+
/** The body's bytes, or null once it exceeds `limit` bytes (declared or read). */
|
|
26
|
+
async function readBoundedBytes(request, limit) {
|
|
22
27
|
if (Number(request.headers.get("Content-Length") ?? "0") > limit) return null;
|
|
23
|
-
if (!request.body) return
|
|
28
|
+
if (!request.body) return /* @__PURE__ */ new Uint8Array();
|
|
24
29
|
const reader = request.body.getReader();
|
|
25
30
|
const chunks = [];
|
|
26
31
|
let size = 0;
|
|
@@ -40,7 +45,7 @@ async function readBoundedBody(request, limit) {
|
|
|
40
45
|
bytes.set(chunk, offset);
|
|
41
46
|
offset += chunk.byteLength;
|
|
42
47
|
}
|
|
43
|
-
return
|
|
48
|
+
return bytes;
|
|
44
49
|
}
|
|
45
50
|
//#endregion
|
|
46
51
|
//#region frontend/src/client-ip.ts
|
|
@@ -200,4 +205,4 @@ function rateLimitKey(address) {
|
|
|
200
205
|
})}/64`;
|
|
201
206
|
}
|
|
202
207
|
//#endregion
|
|
203
|
-
export { readBoundedBody as i, rateLimitKey as n, isSameOrigin as r, clientAddressResolver as t };
|
|
208
|
+
export { readBoundedBytes as a, readBoundedBody as i, rateLimitKey as n, isSameOrigin as r, clientAddressResolver as t };
|
package/dist/consent-html.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { S as SERVICE_CATALOG, T as copyLanguage, c as grantedCategories, d as promptReason, f as readConsentCookie, x as OPTIONAL_CATEGORIES } from "./state-BYWc5TUB.js";
|
|
2
2
|
import { a as CONSENT_COPY, c as legalRevisions, r as escapeHtml } from "./embeds-CJJemAmE.js";
|
|
3
|
-
import { t as CONSENT_UI_VERSION } from "./version-
|
|
3
|
+
import { t as CONSENT_UI_VERSION } from "./version-BZolkooG.js";
|
|
4
4
|
import { t as CONSENT_CONFIG_ID } from "./client-config-B3LNSOxl.js";
|
|
5
5
|
import { t as CONSENT_PATH } from "./paths-Cmzzof3v.js";
|
|
6
6
|
//#region frontend/src/consent/html.ts
|
package/dist/consent-server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { t as bearerMatches } from "./bearer-DMOCoSci.js";
|
|
|
2
2
|
import { i as DECISION_VALIDITY_MS, l as isConsentId, t as CONSENT_ACTIONS, x as OPTIONAL_CATEGORIES } from "./state-BYWc5TUB.js";
|
|
3
3
|
import { a as parseConsentRecordRequest, t as MAX_RECORD_BYTES } from "./records-Dumrz759.js";
|
|
4
4
|
import { t as CONSENT_PATH } from "./paths-Cmzzof3v.js";
|
|
5
|
-
import { i as readBoundedBody, n as rateLimitKey, r as isSameOrigin, t as clientAddressResolver } from "./client-ip-
|
|
5
|
+
import { i as readBoundedBody, n as rateLimitKey, r as isSameOrigin, t as clientAddressResolver } from "./client-ip-NKt34dBM.js";
|
|
6
6
|
//#region frontend/src/consent/server.ts
|
|
7
7
|
const MAX_RETENTION_DAYS = 1826;
|
|
8
8
|
const DAY_MS = 864e5;
|
package/dist/consent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as SERVICE_IDS, D as hostMatches, E as embedService, O as hostService, S as SERVICE_CATALOG, T as copyLanguage, _ as consentConfiguration, a as consentCookie, b as COPY_LANGUAGES, c as grantedCategories, d as promptReason, f as readConsentCookie, g as MEASUREMENT_ID, h as CONSENT_COPY_REVISION, i as DECISION_VALIDITY_MS, k as isServiceId, l as isConsentId, m as serviceGranted, n as CONSENT_COOKIE, o as decide, p as serializeConsentState, r as CONSENT_COOKIE_MAX_AGE_SECONDS, s as denyAll, t as CONSENT_ACTIONS, u as parseConsentState, v as needsBanner, w as catalogService, x as OPTIONAL_CATEGORIES, y as CONSENT_CATEGORIES } from "./state-BYWc5TUB.js";
|
|
2
2
|
import { a as parseConsentRecordRequest, i as isRecordToken, n as consentRecordRequest, r as isLegalRoleName, t as MAX_RECORD_BYTES } from "./records-Dumrz759.js";
|
|
3
3
|
import { a as CONSENT_COPY, c as legalRevisions, i as rewriteEmbeds, l as parseLegalLanguage, n as embedPlaceholder, o as LEGAL_ROLES, r as escapeHtml, s as REQUIRED_LEGAL_ROLES, t as blockedPlaceholder } from "./embeds-CJJemAmE.js";
|
|
4
|
-
import { t as CONSENT_UI_VERSION } from "./version-
|
|
4
|
+
import { t as CONSENT_UI_VERSION } from "./version-BZolkooG.js";
|
|
5
5
|
export { CONSENT_ACTIONS, CONSENT_CATEGORIES, CONSENT_COOKIE, CONSENT_COOKIE_MAX_AGE_SECONDS, CONSENT_COPY, CONSENT_COPY_REVISION, CONSENT_UI_VERSION, COPY_LANGUAGES, DECISION_VALIDITY_MS, LEGAL_ROLES, MAX_RECORD_BYTES, MEASUREMENT_ID, OPTIONAL_CATEGORIES, REQUIRED_LEGAL_ROLES, SERVICE_CATALOG, SERVICE_IDS, blockedPlaceholder, catalogService, consentConfiguration, consentCookie, consentRecordRequest, copyLanguage, decide, denyAll, embedPlaceholder, embedService, escapeHtml, grantedCategories, hostMatches, hostService, isConsentId, isLegalRoleName, isRecordToken, isServiceId, legalRevisions, needsBanner, parseConsentRecordRequest, parseConsentState, parseLegalLanguage, promptReason, readConsentCookie, rewriteEmbeds, serializeConsentState, serviceGranted };
|
|
@@ -21,7 +21,8 @@ const STATUSES = /* @__PURE__ */ new Set([
|
|
|
21
21
|
"invalid",
|
|
22
22
|
"failed",
|
|
23
23
|
"verification",
|
|
24
|
-
"too-many"
|
|
24
|
+
"too-many",
|
|
25
|
+
"too-large"
|
|
25
26
|
]);
|
|
26
27
|
/** The Set-Cookie header that carries a form's outcome to the page. */
|
|
27
28
|
function formStateCookie(state, options) {
|
|
@@ -118,7 +119,10 @@ const FORMS_COPY = {
|
|
|
118
119
|
range: "Enter a number in the allowed range.",
|
|
119
120
|
url: "Enter a valid web address, starting with https://.",
|
|
120
121
|
choice: "Choose one of the options.",
|
|
121
|
-
"too-long": "This is longer than allowed."
|
|
122
|
+
"too-long": "This is longer than allowed.",
|
|
123
|
+
"file-type": "Choose files of an accepted type.",
|
|
124
|
+
"file-size": "A file is larger than allowed.",
|
|
125
|
+
"file-count": "Too many files."
|
|
122
126
|
},
|
|
123
127
|
failed: "Your message couldn't be sent. Please try again later.",
|
|
124
128
|
verification: "We couldn't verify that you're not a robot. Please try again.",
|
|
@@ -128,7 +132,12 @@ const FORMS_COPY = {
|
|
|
128
132
|
submit: "Submit",
|
|
129
133
|
privacyPolicy: "Privacy policy",
|
|
130
134
|
honeypot: "This field is for validation purposes and should be left unchanged.",
|
|
131
|
-
needsJavaScript: "This form needs JavaScript to verify you're not a robot."
|
|
135
|
+
needsJavaScript: "This form needs JavaScript to verify you're not a robot.",
|
|
136
|
+
fileTypes: "Accepted file types: {types}.",
|
|
137
|
+
fileSize: "Up to {size} per file.",
|
|
138
|
+
fileCount: "Up to {count} files.",
|
|
139
|
+
filesAgain: "Choose your files again.",
|
|
140
|
+
tooLarge: "The files are too large to send together. Choose fewer or smaller files."
|
|
132
141
|
},
|
|
133
142
|
"pt-PT": {
|
|
134
143
|
required: "obrigatório",
|
|
@@ -142,7 +151,10 @@ const FORMS_COPY = {
|
|
|
142
151
|
range: "Introduza um número dentro do intervalo permitido.",
|
|
143
152
|
url: "Introduza um endereço web válido, começado por https://.",
|
|
144
153
|
choice: "Escolha uma das opções.",
|
|
145
|
-
"too-long": "Excede o comprimento permitido."
|
|
154
|
+
"too-long": "Excede o comprimento permitido.",
|
|
155
|
+
"file-type": "Escolha ficheiros de um tipo aceite.",
|
|
156
|
+
"file-size": "Um ficheiro excede o tamanho permitido.",
|
|
157
|
+
"file-count": "Demasiados ficheiros."
|
|
146
158
|
},
|
|
147
159
|
failed: "Não foi possível enviar a sua mensagem. Tente novamente mais tarde.",
|
|
148
160
|
verification: "Não foi possível verificar que não é um robô. Tente novamente.",
|
|
@@ -152,7 +164,12 @@ const FORMS_COPY = {
|
|
|
152
164
|
submit: "Enviar",
|
|
153
165
|
privacyPolicy: "Política de privacidade",
|
|
154
166
|
honeypot: "Este campo serve para validação e deve ser deixado em branco.",
|
|
155
|
-
needsJavaScript: "Este formulário precisa de JavaScript para verificar que não é um robô."
|
|
167
|
+
needsJavaScript: "Este formulário precisa de JavaScript para verificar que não é um robô.",
|
|
168
|
+
fileTypes: "Tipos de ficheiro aceites: {types}.",
|
|
169
|
+
fileSize: "Até {size} por ficheiro.",
|
|
170
|
+
fileCount: "Até {count} ficheiros.",
|
|
171
|
+
filesAgain: "Volte a escolher os ficheiros.",
|
|
172
|
+
tooLarge: "Os ficheiros são demasiado grandes para enviar em conjunto. Escolha menos ficheiros ou ficheiros mais pequenos."
|
|
156
173
|
}
|
|
157
174
|
};
|
|
158
175
|
/** The copy language for a page's `lang`: Portuguese for `pt*`, else English. */
|
|
@@ -6,7 +6,7 @@ declare const FORM_DESCRIPTOR_SCHEMA = 1;
|
|
|
6
6
|
/** The Gravity Forms block a Site's dispatcher routes to the renderer. */
|
|
7
7
|
declare const GRAVITY_FORMS_BLOCK = "gravityforms/form";
|
|
8
8
|
/** Field types this release renders and submits. */
|
|
9
|
-
declare const SUPPORTED_FIELD_TYPES: readonly ["text", "textarea", "email", "phone", "number", "website", "select", "radio", "checkbox", "consent", "name", "hidden", "html", "section"];
|
|
9
|
+
declare const SUPPORTED_FIELD_TYPES: readonly ["text", "textarea", "email", "phone", "number", "website", "select", "radio", "checkbox", "consent", "name", "hidden", "html", "section", "fileupload"];
|
|
10
10
|
type FormFieldType = (typeof SUPPORTED_FIELD_TYPES)[number];
|
|
11
11
|
interface FormChoice {
|
|
12
12
|
text: string;
|
|
@@ -34,6 +34,23 @@ interface FormConsent {
|
|
|
34
34
|
/** Gravity Forms' form revision the text belongs to; submitted with the consent. */
|
|
35
35
|
revisionId: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The extensions an upload field may allow: the formats whose content the form
|
|
39
|
+
* endpoint verifies (ADR 0010 here). Another extension is a diagnostic.
|
|
40
|
+
*/
|
|
41
|
+
declare const VERIFIABLE_FILE_EXTENSIONS: readonly ["jpg", "jpeg", "png", "gif", "webp", "pdf"];
|
|
42
|
+
type VerifiableFileExtension = (typeof VERIFIABLE_FILE_EXTENSIONS)[number];
|
|
43
|
+
/** A file upload field's public settings, as Gravity Forms enforces them. */
|
|
44
|
+
interface FormUpload {
|
|
45
|
+
/** Lower case, without dots (`jpg`, `pdf`). Empty: none set, which isn't supported. */
|
|
46
|
+
allowedExtensions: string[];
|
|
47
|
+
/** The largest file accepted, in bytes (the field's limit, capped by the CMS's PHP). */
|
|
48
|
+
maxFileSize: number;
|
|
49
|
+
/** Whether the field takes several files. */
|
|
50
|
+
multipleFiles: boolean;
|
|
51
|
+
/** The most files it takes: 1 for a single file; 0 for several without a maximum (unsupported). */
|
|
52
|
+
maxFiles: number;
|
|
53
|
+
}
|
|
37
54
|
interface FormField {
|
|
38
55
|
id: number;
|
|
39
56
|
type: FormFieldType;
|
|
@@ -61,6 +78,8 @@ interface FormField {
|
|
|
61
78
|
};
|
|
62
79
|
/** The Site's CSS classes the editor set on the field. */
|
|
63
80
|
cssClass?: string;
|
|
81
|
+
/** fileupload only. */
|
|
82
|
+
upload?: FormUpload;
|
|
64
83
|
}
|
|
65
84
|
type FormConfirmation = {
|
|
66
85
|
type: "message";
|
|
@@ -114,6 +133,8 @@ declare function blockFormId(attributes: Record<string, unknown> | null | undefi
|
|
|
114
133
|
declare function findForm(forms: readonly FormDescriptor[] | null | undefined, formId: number): FormDescriptor | null;
|
|
115
134
|
/** The path a form posts to on the Site's own origin. */
|
|
116
135
|
declare const formAction: (formId: number) => string;
|
|
136
|
+
/** Whether a descriptor has an upload field a visitor fills in: its form posts multipart. */
|
|
137
|
+
declare function hasUploads(descriptor: FormDescriptor): boolean;
|
|
117
138
|
/** Everything readiness must fail for: an unavailable form, and each unsupported field or setting. */
|
|
118
139
|
declare function formDiagnostics(descriptor: FormDescriptor): FormDiagnostic[];
|
|
119
140
|
/** The spam controls a Site's forms use. */
|
|
@@ -128,16 +149,25 @@ interface FormsReadiness {
|
|
|
128
149
|
/** Gravity Forms' honeypot is enabled on the form. */
|
|
129
150
|
honeypot: boolean;
|
|
130
151
|
}>;
|
|
131
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Controls the Site declares that no checked form uses (honeypot only), and
|
|
154
|
+
* forms whose largest upload exceeds the Site's `maxUploadBytes`.
|
|
155
|
+
*/
|
|
132
156
|
warnings: string[];
|
|
133
157
|
}
|
|
158
|
+
/** The most a form's visible upload fields can post together (bytes): each field's limit times its files. */
|
|
159
|
+
declare function largestUpload(descriptor: FormDescriptor): number;
|
|
134
160
|
/**
|
|
135
161
|
* Readiness over the forms a Site's published content references: fails for
|
|
136
162
|
* any diagnostic. `spam` is the Site's declared controls; declaring the
|
|
137
163
|
* honeypot for a form that doesn't enable it is reported as a warning.
|
|
164
|
+
* `maxUploadBytes` is the Site's cap on a post (the form endpoint's): a form
|
|
165
|
+
* whose files can exceed it together is reported as a warning, since its
|
|
166
|
+
* largest posts will be refused.
|
|
138
167
|
*/
|
|
139
168
|
declare function formsReadiness(descriptors: readonly FormDescriptor[], options?: {
|
|
140
169
|
spam?: readonly FormSpamControl[];
|
|
170
|
+
maxUploadBytes?: number;
|
|
141
171
|
}): FormsReadiness;
|
|
142
172
|
//#endregion
|
|
143
|
-
export { parseFormDescriptors as S,
|
|
173
|
+
export { formsReadiness as C, parseFormDescriptors as D, parseFormDescriptor as E, formDiagnostics as S, largestUpload as T, VERIFIABLE_FILE_EXTENSIONS as _, FormConsent as a, findForm as b, FormDiagnosticCode as c, FormInput as d, FormSpamControl as f, SUPPORTED_FIELD_TYPES as g, GRAVITY_FORMS_BLOCK as h, FormConfirmation as i, FormField as l, FormsReadiness as m, FORM_DESCRIPTOR_SCHEMA as n, FormDescriptor as o, FormUpload as p, FormChoice as r, FormDiagnostic as s, FORMS_PATH as t, FormFieldType as u, VerifiableFileExtension as v, hasUploads as w, formAction as x, blockFormId as y };
|
|
@@ -20,7 +20,20 @@ const SUPPORTED_FIELD_TYPES = [
|
|
|
20
20
|
"name",
|
|
21
21
|
"hidden",
|
|
22
22
|
"html",
|
|
23
|
-
"section"
|
|
23
|
+
"section",
|
|
24
|
+
"fileupload"
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* The extensions an upload field may allow: the formats whose content the form
|
|
28
|
+
* endpoint verifies (ADR 0010 here). Another extension is a diagnostic.
|
|
29
|
+
*/
|
|
30
|
+
const VERIFIABLE_FILE_EXTENSIONS = [
|
|
31
|
+
"jpg",
|
|
32
|
+
"jpeg",
|
|
33
|
+
"png",
|
|
34
|
+
"gif",
|
|
35
|
+
"webp",
|
|
36
|
+
"pdf"
|
|
24
37
|
];
|
|
25
38
|
function isRecord(value) {
|
|
26
39
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -53,6 +66,20 @@ function parseInput(value) {
|
|
|
53
66
|
...optionalText(value.autocomplete) ? { autocomplete: text(value.autocomplete) } : {}
|
|
54
67
|
};
|
|
55
68
|
}
|
|
69
|
+
const EXTENSION = /^[a-z0-9]{1,16}$/;
|
|
70
|
+
function parseUpload(value) {
|
|
71
|
+
const record = isRecord(value) ? value : {};
|
|
72
|
+
const extensions = Array.isArray(record.allowedExtensions) ? record.allowedExtensions : [];
|
|
73
|
+
const multipleFiles = record.multipleFiles === true;
|
|
74
|
+
const size = finite(record.maxFileSize);
|
|
75
|
+
const count = finite(record.maxFiles);
|
|
76
|
+
return {
|
|
77
|
+
allowedExtensions: [...new Set(extensions.map((each) => text(each).trim().toLowerCase().replace(/^\./, "")).filter((each) => EXTENSION.test(each)))],
|
|
78
|
+
maxFileSize: size !== void 0 && size > 0 ? Math.floor(size) : 0,
|
|
79
|
+
multipleFiles,
|
|
80
|
+
maxFiles: !multipleFiles ? 1 : count !== void 0 && count > 0 ? Math.floor(count) : 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
56
83
|
function parseField(value) {
|
|
57
84
|
if (!isRecord(value)) return null;
|
|
58
85
|
const id = value.id;
|
|
@@ -93,6 +120,7 @@ function parseField(value) {
|
|
|
93
120
|
...max !== void 0 ? { max } : {}
|
|
94
121
|
};
|
|
95
122
|
}
|
|
123
|
+
if (field.type === "fileupload") field.upload = parseUpload(value.upload);
|
|
96
124
|
return field;
|
|
97
125
|
}
|
|
98
126
|
function parseConfirmation(value) {
|
|
@@ -169,22 +197,53 @@ function findForm(forms, formId) {
|
|
|
169
197
|
}
|
|
170
198
|
/** The path a form posts to on the Site's own origin. */
|
|
171
199
|
const formAction = (formId) => `${FORMS_PATH}/${formId}`;
|
|
200
|
+
/** Whether a descriptor has an upload field a visitor fills in: its form posts multipart. */
|
|
201
|
+
function hasUploads(descriptor) {
|
|
202
|
+
return descriptor.fields.some((field) => field.type === "fileupload" && field.visibility === "visible");
|
|
203
|
+
}
|
|
204
|
+
/** Why an upload field's settings can't be honoured, or null. */
|
|
205
|
+
function uploadProblem(upload) {
|
|
206
|
+
if (!upload || upload.allowedExtensions.length === 0) return `Set the field's allowed file extensions (${VERIFIABLE_FILE_EXTENSIONS.join(", ")}).`;
|
|
207
|
+
const unverifiable = upload.allowedExtensions.filter((each) => !VERIFIABLE_FILE_EXTENSIONS.includes(each));
|
|
208
|
+
if (unverifiable.length) return `The Frontend can't verify ${unverifiable.join(", ")} files: allow only ${VERIFIABLE_FILE_EXTENSIONS.join(", ")}.`;
|
|
209
|
+
if (upload.maxFileSize <= 0) return "The field has no file size limit.";
|
|
210
|
+
if (upload.maxFiles <= 0) return "Set the maximum number of files.";
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
172
213
|
/** Everything readiness must fail for: an unavailable form, and each unsupported field or setting. */
|
|
173
214
|
function formDiagnostics(descriptor) {
|
|
174
215
|
const diagnostics = [...descriptor.diagnostics];
|
|
216
|
+
for (const field of descriptor.fields) {
|
|
217
|
+
if (field.type !== "fileupload") continue;
|
|
218
|
+
const problem = uploadProblem(field.upload);
|
|
219
|
+
if (problem && !diagnostics.some((each) => each.code === "unsupported-file-upload" && each.fieldId === field.id)) diagnostics.push({
|
|
220
|
+
code: "unsupported-file-upload",
|
|
221
|
+
fieldId: field.id,
|
|
222
|
+
type: "fileupload",
|
|
223
|
+
message: problem
|
|
224
|
+
});
|
|
225
|
+
}
|
|
175
226
|
if (!descriptor.available && !diagnostics.some((each) => each.code === "unavailable")) diagnostics.unshift({
|
|
176
227
|
code: "unavailable",
|
|
177
228
|
message: `Form ${descriptor.id} is missing, inactive or in the trash.`
|
|
178
229
|
});
|
|
179
230
|
return diagnostics;
|
|
180
231
|
}
|
|
232
|
+
/** The most a form's visible upload fields can post together (bytes): each field's limit times its files. */
|
|
233
|
+
function largestUpload(descriptor) {
|
|
234
|
+
return descriptor.fields.filter((field) => field.type === "fileupload" && field.visibility === "visible").reduce((total, field) => total + (field.upload?.maxFileSize ?? 0) * Math.max(field.upload?.maxFiles ?? 0, 1), 0);
|
|
235
|
+
}
|
|
181
236
|
/**
|
|
182
237
|
* Readiness over the forms a Site's published content references: fails for
|
|
183
238
|
* any diagnostic. `spam` is the Site's declared controls; declaring the
|
|
184
239
|
* honeypot for a form that doesn't enable it is reported as a warning.
|
|
240
|
+
* `maxUploadBytes` is the Site's cap on a post (the form endpoint's): a form
|
|
241
|
+
* whose files can exceed it together is reported as a warning, since its
|
|
242
|
+
* largest posts will be refused.
|
|
185
243
|
*/
|
|
186
244
|
function formsReadiness(descriptors, options = {}) {
|
|
187
|
-
const
|
|
245
|
+
const unique = new Map(descriptors.map((descriptor) => [descriptor.id, descriptor]));
|
|
246
|
+
const forms = [...unique.values()].map((descriptor) => {
|
|
188
247
|
const diagnostics = formDiagnostics(descriptor);
|
|
189
248
|
return {
|
|
190
249
|
id: descriptor.id,
|
|
@@ -195,6 +254,11 @@ function formsReadiness(descriptors, options = {}) {
|
|
|
195
254
|
};
|
|
196
255
|
});
|
|
197
256
|
const warnings = options.spam?.includes("honeypot") ? forms.filter((form) => !form.honeypot).map((form) => `Form ${form.id} doesn't enable Gravity Forms' anti-spam honeypot.`) : [];
|
|
257
|
+
const cap = options.maxUploadBytes;
|
|
258
|
+
if (cap !== void 0) for (const descriptor of unique.values()) {
|
|
259
|
+
const largest = largestUpload(descriptor);
|
|
260
|
+
if (largest > cap) warnings.push(`Form ${descriptor.id}'s files can reach ${(largest / 1048576).toFixed(1)} MB together, more than the ${(cap / 1048576).toFixed(1)} MB the Site accepts per post: lower its upload fields' size limits or numbers of files.`);
|
|
261
|
+
}
|
|
198
262
|
return {
|
|
199
263
|
ok: forms.every((form) => form.ok),
|
|
200
264
|
forms,
|
|
@@ -202,4 +266,4 @@ function formsReadiness(descriptors, options = {}) {
|
|
|
202
266
|
};
|
|
203
267
|
}
|
|
204
268
|
//#endregion
|
|
205
|
-
export {
|
|
269
|
+
export { VERIFIABLE_FILE_EXTENSIONS as a, formAction as c, hasUploads as d, largestUpload as f, SUPPORTED_FIELD_TYPES as i, formDiagnostics as l, parseFormDescriptors as m, FORM_DESCRIPTOR_SCHEMA as n, blockFormId as o, parseFormDescriptor as p, GRAVITY_FORMS_BLOCK as r, findForm as s, FORMS_PATH as t, formsReadiness as u };
|
|
@@ -167083,8 +167083,7 @@ var presentation_default = {
|
|
|
167083
167083
|
"paddingInline": "reset",
|
|
167084
167084
|
"marginBlock": "reset",
|
|
167085
167085
|
"marginInline": "reset",
|
|
167086
|
-
"
|
|
167087
|
-
"listStylePosition": "outside"
|
|
167086
|
+
"listStylePosition": "inside"
|
|
167088
167087
|
},
|
|
167089
167088
|
"core/list-item": {
|
|
167090
167089
|
"fontFamily": "reset",
|
|
@@ -167224,6 +167223,21 @@ var presentation_default = {
|
|
|
167224
167223
|
"paddingInline": ["paddingRight", "paddingLeft"]
|
|
167225
167224
|
},
|
|
167226
167225
|
targets: {
|
|
167226
|
+
"core/list": {
|
|
167227
|
+
"elements": {
|
|
167228
|
+
"unordered": [":is(ul)"],
|
|
167229
|
+
"ordered": [":is(ol)"]
|
|
167230
|
+
},
|
|
167231
|
+
"rules": [{
|
|
167232
|
+
"target": "unordered",
|
|
167233
|
+
"important": true,
|
|
167234
|
+
"declarations": { "list-style-type": "disc" }
|
|
167235
|
+
}, {
|
|
167236
|
+
"target": "ordered",
|
|
167237
|
+
"important": true,
|
|
167238
|
+
"declarations": { "list-style-type": "decimal" }
|
|
167239
|
+
}]
|
|
167240
|
+
},
|
|
167227
167241
|
"core/image": {
|
|
167228
167242
|
"selector": ".wp-block-image",
|
|
167229
167243
|
"elements": {
|
|
@@ -167839,8 +167853,9 @@ function presentationStyleSheet(theme = {}, options = {}) {
|
|
|
167839
167853
|
const flowSelector = (name, respectNativeMargin = false) => capabilities[name] ? selectorOf(name) : `.${ownClassOf(name)}${respectNativeMargin ? `:not(${nativeMargin})` : ""}`;
|
|
167840
167854
|
const flowBlocks = (respectNativeMargin) => `:is(${(flow?.blocks ?? []).map((name) => flowSelector(name, respectNativeMargin)).join(",")})`;
|
|
167841
167855
|
const flowRule = flow ? `:where(:not(${nonFlowParents}) > * + ${flowBlocks(true)}){margin-block-start:${flow.gap} !important;}` + (flow.layout?.length ? `:where(${flowBlocks(false)} + :is(${flow.layout.map((name) => flowSelector(name)).join(",")}):not(:is(header,footer,nav,${nonFlowParents}) *)){margin-block-start:${flow.gap} !important;}` : "") : "";
|
|
167856
|
+
const editorRule = options.editor ? `li.gqd-presentation > .block-editor-rich-text__editable{display:inline;}` : "";
|
|
167842
167857
|
for (const [breakpoint, threshold] of Object.entries(breakpoints)) {
|
|
167843
|
-
let rule = breakpoint === "base" ? flowRule : "";
|
|
167858
|
+
let rule = breakpoint === "base" ? flowRule + editorRule : "";
|
|
167844
167859
|
applicable.push(breakpoint);
|
|
167845
167860
|
for (const [name, supported] of Object.entries(capabilities)) {
|
|
167846
167861
|
const target = configuredModel.targets?.[name];
|
package/dist/forms-astro.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as clearFormStateCookie, l as readFormState, n as FORM_STATE_PARAM, r as FormState } from "./state-
|
|
1
|
+
import { b as findForm, h as GRAVITY_FORMS_BLOCK, l as FormField, o as FormDescriptor, y as blockFormId } from "./descriptor-BF5Y9a1J.js";
|
|
2
|
+
import { a as clearFormStateCookie, l as readFormState, n as FORM_STATE_PARAM, r as FormState } from "./state-Bw-HSBeO.js";
|
|
3
3
|
//#region frontend/src/forms/astro.d.ts
|
|
4
4
|
/** LEGAL-1's links in the page's language (the chrome's `legal`), structurally. */
|
|
5
5
|
export interface FormsLegal {
|
|
@@ -33,6 +33,8 @@ export interface GravityFormInput {
|
|
|
33
33
|
export declare function escapeHtml(value: string): string;
|
|
34
34
|
/** The HTML id of an input (`gq-form-1-input_1_3`). */
|
|
35
35
|
export declare const inputId: (formId: number, name: string) => string;
|
|
36
|
+
/** A file size as visitors read it (5 MB, 512 KB), never more than the limit. */
|
|
37
|
+
export declare function formatFileSize(bytes: number, language: string): string;
|
|
36
38
|
/** Turnstile loads only once the visitor interacts with a form (CONSENT-1, CN-R3). */
|
|
37
39
|
export declare const FORMS_TURNSTILE_SCRIPT = "(()=>{const s=\"https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit\";let l;const load=()=>l??=new Promise((ok,no)=>{const e=document.createElement(\"script\");e.src=s;e.async=true;e.onload=()=>ok(window.turnstile);e.onerror=no;document.head.append(e)});for(const f of document.querySelectorAll(\"form[data-gq-turnstile]\")){const start=()=>{if(f.dataset.gqTurnstileStarted)return;f.dataset.gqTurnstileStarted=\"1\";load().then(t=>t.render(f.querySelector(\"[data-gq-turnstile-widget]\"),{sitekey:f.dataset.gqTurnstile,\"response-field-name\":\"cf-turnstile-response\"}))};f.addEventListener(\"focusin\",start,{once:true});f.addEventListener(\"pointerdown\",start,{once:true})}})();";
|
|
38
40
|
/**
|
package/dist/forms-astro.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as clearFormStateCookie, i as FORM_STATE_PARAM, l as readFormState, n as formsCopyLanguage, t as FORMS_COPY } from "./copy-
|
|
1
|
+
import { c as formAction, d as hasUploads, o as blockFormId, r as GRAVITY_FORMS_BLOCK, s as findForm } from "./descriptor-DXmLmjEa.js";
|
|
2
|
+
import { a as clearFormStateCookie, i as FORM_STATE_PARAM, l as readFormState, n as formsCopyLanguage, t as FORMS_COPY } from "./copy-CfNr6M73.js";
|
|
3
3
|
//#region frontend/src/forms/astro.ts
|
|
4
4
|
function escapeHtml(value) {
|
|
5
5
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
@@ -41,7 +41,29 @@ function shown(name, field, context) {
|
|
|
41
41
|
function requiredMark(field, copy) {
|
|
42
42
|
return field.required ? ` <span class="gq-form__required" aria-hidden="true">*</span><span class="gq-form__visually-hidden"> (${escapeHtml(copy.required)})</span>` : "";
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
44
|
+
/** A file size as visitors read it (5 MB, 512 KB), never more than the limit. */
|
|
45
|
+
function formatFileSize(bytes, language) {
|
|
46
|
+
let format;
|
|
47
|
+
try {
|
|
48
|
+
format = new Intl.NumberFormat(language, { maximumFractionDigits: 1 });
|
|
49
|
+
} catch {
|
|
50
|
+
format = new Intl.NumberFormat("en", { maximumFractionDigits: 1 });
|
|
51
|
+
}
|
|
52
|
+
const megabytes = bytes / 1048576;
|
|
53
|
+
if (megabytes >= 1) return `${format.format(Math.floor(megabytes * 10) / 10)} MB`;
|
|
54
|
+
return `${format.format(Math.max(1, Math.floor(bytes / 1024)))} KB`;
|
|
55
|
+
}
|
|
56
|
+
/** An upload field's hint: the accepted types, the size limit and how many files. */
|
|
57
|
+
function uploadHint(upload, context) {
|
|
58
|
+
const { copy } = context;
|
|
59
|
+
const parts = [];
|
|
60
|
+
if (upload.allowedExtensions.length) parts.push(copy.fileTypes.replace("{types}", upload.allowedExtensions.map((each) => each.toUpperCase()).join(", ")));
|
|
61
|
+
if (upload.maxFileSize > 0) parts.push(copy.fileSize.replace("{size}", formatFileSize(upload.maxFileSize, context.language)));
|
|
62
|
+
if (upload.multipleFiles && upload.maxFiles > 1) parts.push(copy.fileCount.replace("{count}", String(upload.maxFiles)));
|
|
63
|
+
if (context.state && context.state.status !== "sent") parts.push(copy.filesAgain);
|
|
64
|
+
return parts.join(" ");
|
|
65
|
+
}
|
|
66
|
+
/** The description, hint and error elements of a field, and the ids that describe its input. */
|
|
45
67
|
function annotations(field, context) {
|
|
46
68
|
const base = `gq-form-${context.formId}-field-${field.id}`;
|
|
47
69
|
const error = errorText(field, context);
|
|
@@ -51,6 +73,11 @@ function annotations(field, context) {
|
|
|
51
73
|
ids.push(`${base}-description`);
|
|
52
74
|
html += `<p class="gq-form__description" id="${base}-description">${escapeHtml(field.description)}</p>`;
|
|
53
75
|
}
|
|
76
|
+
const hint = field.upload ? uploadHint(field.upload, context) : "";
|
|
77
|
+
if (hint) {
|
|
78
|
+
ids.push(`${base}-hint`);
|
|
79
|
+
html += `<p class="gq-form__hint" id="${base}-hint">${escapeHtml(hint)}</p>`;
|
|
80
|
+
}
|
|
54
81
|
if (error) {
|
|
55
82
|
ids.push(`${base}-error`);
|
|
56
83
|
html += `<p class="gq-form__error" id="${base}-error">${escapeHtml(error)}</p>`;
|
|
@@ -107,7 +134,10 @@ function renderField(field, context) {
|
|
|
107
134
|
const id = inputId(formId, first.name);
|
|
108
135
|
const common = attribute("placeholder", field.placeholder) + (field.maxLength ? ` maxlength="${field.maxLength}"` : "") + inputAttributes(field, context, notes);
|
|
109
136
|
let control;
|
|
110
|
-
if (field.type === "
|
|
137
|
+
if (field.type === "fileupload") {
|
|
138
|
+
const accept = (field.upload?.allowedExtensions ?? []).map((each) => `.${each}`).join(",");
|
|
139
|
+
control = `<input type="file" id="${id}" name="${escapeHtml(first.name)}"${attribute("accept", accept || null)}${field.upload?.multipleFiles ? " multiple" : ""}${inputAttributes(field, context, notes)}>`;
|
|
140
|
+
} else if (field.type === "textarea") control = `<textarea id="${id}" name="${escapeHtml(first.name)}" rows="6"${common}>${escapeHtml(shown(first.name, field, context))}</textarea>`;
|
|
111
141
|
else if (field.type === "select") {
|
|
112
142
|
const value = typed(context) ? shown(first.name, field, context) : null;
|
|
113
143
|
const choices = field.choices ?? [];
|
|
@@ -145,6 +175,7 @@ function renderGravityForm(input) {
|
|
|
145
175
|
const policy = input.legal?.roles.find((role) => role.role === "privacy_policy");
|
|
146
176
|
const context = {
|
|
147
177
|
formId,
|
|
178
|
+
language: input.language,
|
|
148
179
|
copy,
|
|
149
180
|
state,
|
|
150
181
|
privacyPolicy: policy ? {
|
|
@@ -166,13 +197,15 @@ function renderGravityForm(input) {
|
|
|
166
197
|
const target = field.inputs[0];
|
|
167
198
|
return [`<li><a href="#${!target ? `gq-form-${formId}` : field.type === "radio" || field.type === "checkbox" ? `${inputId(formId, target.name)}-0` : inputId(formId, target.name)}">${escapeHtml(field.label)}: ${escapeHtml(error)}</a></li>`];
|
|
168
199
|
});
|
|
169
|
-
status = `<div class="gq-form__errors" role="alert" id="gq-form-${formId}-errors" tabindex="-1"><p>${escapeHtml(state.status === "invalid" ? copy.errorSummary : state.status === "verification" ? copy.verification : state.status === "too-many" ? copy.tooMany : copy.failed)}</p>${problems.length ? `<ul>${problems.join("")}</ul>` : ""}</div>`;
|
|
200
|
+
status = `<div class="gq-form__errors" role="alert" id="gq-form-${formId}-errors" tabindex="-1"><p>${escapeHtml(state.status === "invalid" ? copy.errorSummary : state.status === "verification" ? copy.verification : state.status === "too-many" ? copy.tooMany : state.status === "too-large" ? copy.tooLarge : copy.failed)}</p>${problems.length ? `<ul>${problems.join("")}</ul>` : ""}</div>`;
|
|
170
201
|
}
|
|
171
202
|
const required = descriptor.fields.some((field) => field.required && field.visibility === "visible") ? `<p class="gq-form__legend">${escapeHtml(copy.requiredFields)}</p>` : "";
|
|
172
203
|
const honeypot = descriptor.honeypot ? `<div class="gq-form__hp" aria-hidden="true" style="position:absolute!important;left:-9999px!important;width:1px;height:1px;overflow:hidden"><label for="${inputId(formId, descriptor.honeypot.name)}">${escapeHtml(copy.honeypot)}</label><input type="text" id="${inputId(formId, descriptor.honeypot.name)}" name="${escapeHtml(descriptor.honeypot.name)}" value="" tabindex="-1" autocomplete="off"></div>` : "";
|
|
173
204
|
const turnstile = input.turnstile?.siteKey ? `<div class="gq-form__turnstile" data-gq-turnstile-widget></div><noscript><p class="gq-form__status">${escapeHtml(copy.needsJavaScript)}</p></noscript>` : "";
|
|
174
205
|
const script = input.turnstile?.siteKey ? `<script>${FORMS_TURNSTILE_SCRIPT}<\/script>` : "";
|
|
175
|
-
|
|
206
|
+
const uploads = hasUploads(descriptor);
|
|
207
|
+
const action = uploads ? `${formAction(formId)}?gq_source=${encodeURIComponent(input.source)}` : formAction(formId);
|
|
208
|
+
return open + status + `<form class="gq-form__form" method="post" action="${escapeHtml(action)}"` + (uploads ? ` enctype="multipart/form-data"` : "") + ` novalidate` + (heading ? ` aria-labelledby="${titleId}"` : "") + attribute("data-gq-turnstile", input.turnstile?.siteKey || null) + `><input type="hidden" name="gq_source" value="${escapeHtml(input.source)}">` + required + honeypot + descriptor.fields.map((field) => renderField(field, context)).join("") + turnstile + `<div class="gq-form__actions"><button type="submit" class="gq-form__submit">${escapeHtml(descriptor.submitLabel || copy.submit)}</button></div></form></div>` + script;
|
|
176
209
|
}
|
|
177
210
|
/**
|
|
178
211
|
* A minimal default stylesheet (layout and states only). Sites usually style
|
|
@@ -190,4 +223,4 @@ const FORMS_STYLES = `
|
|
|
190
223
|
.gq-form :focus-visible{outline:3px solid var(--gq-form-focus,#1a73e8);outline-offset:2px}
|
|
191
224
|
`.trim();
|
|
192
225
|
//#endregion
|
|
193
|
-
export { FORMS_STYLES, FORMS_TURNSTILE_SCRIPT, FORM_STATE_PARAM, GRAVITY_FORMS_BLOCK, blockFormId, clearFormStateCookie, escapeHtml, findForm, inputId, readFormState, renderGravityForm };
|
|
226
|
+
export { FORMS_STYLES, FORMS_TURNSTILE_SCRIPT, FORM_STATE_PARAM, GRAVITY_FORMS_BLOCK, blockFormId, clearFormStateCookie, escapeHtml, findForm, formatFileSize, inputId, readFormState, renderGravityForm };
|
package/dist/forms-server.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { o as FormDescriptor, t as FORMS_PATH } from "./descriptor-
|
|
1
|
+
import { o as FormDescriptor, t as FORMS_PATH } from "./descriptor-BF5Y9a1J.js";
|
|
2
2
|
//#region frontend/src/forms/server.d.ts
|
|
3
3
|
/** The largest form body accepted (bytes); larger posts are refused unread. */
|
|
4
4
|
export declare const MAX_FORM_BYTES: number;
|
|
5
|
+
/** The largest multipart (upload) body accepted by default (bytes), files included. */
|
|
6
|
+
export declare const MAX_UPLOAD_BYTES: number;
|
|
7
|
+
/** The most `maxUploadBytes` may be: the Worker holds a post in memory. */
|
|
8
|
+
export declare const MAX_UPLOAD_BYTES_LIMIT: number;
|
|
5
9
|
/** Cloudflare's server-side Turnstile verification. */
|
|
6
10
|
export declare const TURNSTILE_SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
7
11
|
/** The field Turnstile's widget posts its token in. */
|
|
@@ -58,7 +62,13 @@ export interface FormsServerConfig {
|
|
|
58
62
|
trustedProxies?: readonly string[];
|
|
59
63
|
/** The page language for the handler's own messages (`pt-PT`, `en`). */
|
|
60
64
|
language?: string;
|
|
61
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* The hard cap on a multipart post, files included (bytes; 10 MiB, at most
|
|
67
|
+
* 32 MiB). Keep it within what the Site's proxies and the CMS's PHP
|
|
68
|
+
* (`post_max_size`) accept.
|
|
69
|
+
*/
|
|
70
|
+
maxUploadBytes?: number;
|
|
71
|
+
/** Milliseconds Gravity Forms and Turnstile have to answer (15 s; 60 s for uploads). */
|
|
62
72
|
timeoutMs?: number;
|
|
63
73
|
fetch?: typeof globalThis.fetch;
|
|
64
74
|
}
|
|
@@ -79,6 +89,7 @@ export declare function createFormsServer(config: FormsServerConfig): {
|
|
|
79
89
|
turnstileSecret: boolean;
|
|
80
90
|
access: string;
|
|
81
91
|
rateLimit: boolean;
|
|
92
|
+
maxUploadBytes: number;
|
|
82
93
|
ready: boolean;
|
|
83
94
|
}>;
|
|
84
95
|
};
|