@getquick/content 0.5.0 → 0.6.0

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/dist/forms.d.ts CHANGED
@@ -1,5 +1,43 @@
1
- import { S as parseFormDescriptors, _ as findForm, a as FormConsent, b as formsReadiness, c as FormDiagnosticCode, d as FormInput, f as FormSpamControl, g as blockFormId, h as SUPPORTED_FIELD_TYPES, i as FormConfirmation, l as FormField, m as GRAVITY_FORMS_BLOCK, n as FORM_DESCRIPTOR_SCHEMA, o as FormDescriptor, p as FormsReadiness, r as FormChoice, s as FormDiagnostic, t as FORMS_PATH, u as FormFieldType, v as formAction, x as parseFormDescriptor, y as formDiagnostics } from "./descriptor-B-e7gHi1.js";
2
- import { a as clearFormStateCookie, c as formStateLocation, d as FormErrorCode, f as FormsCopy, i as FormStatus, l as readFormState, m as formsCopyLanguage, n as FORM_STATE_PARAM, o as formStateCookie, p as FormsCopyLanguage, r as FormState, s as formStateCookieName, t as FORM_STATE_MAX_AGE, u as FORMS_COPY } from "./state-CsMFLUUO.js";
1
+ import { C as formsReadiness, D as parseFormDescriptors, E as parseFormDescriptor, S as formDiagnostics, T as largestUpload, _ as VERIFIABLE_FILE_EXTENSIONS, a as FormConsent, b as findForm, c as FormDiagnosticCode, d as FormInput, f as FormSpamControl, g as SUPPORTED_FIELD_TYPES, h as GRAVITY_FORMS_BLOCK, i as FormConfirmation, l as FormField, m as FormsReadiness, n as FORM_DESCRIPTOR_SCHEMA, o as FormDescriptor, p as FormUpload, r as FormChoice, s as FormDiagnostic, t as FORMS_PATH, u as FormFieldType, v as VerifiableFileExtension, w as hasUploads, x as formAction, y as blockFormId } from "./descriptor-BF5Y9a1J.js";
2
+ import { a as clearFormStateCookie, c as formStateLocation, d as FormErrorCode, f as FormsCopy, i as FormStatus, l as readFormState, m as formsCopyLanguage, n as FORM_STATE_PARAM, o as formStateCookie, p as FormsCopyLanguage, r as FormState, s as formStateCookieName, t as FORM_STATE_MAX_AGE, u as FORMS_COPY } from "./state-Bw-HSBeO.js";
3
+ //#region frontend/src/forms/uploads.d.ts
4
+ /** The format each allowed extension's content must prove, as a MIME type. */
5
+ export declare const FILE_TYPES: Readonly<Record<VerifiableFileExtension, string>>;
6
+ /** The format a file's first bytes prove (a MIME type of FILE_TYPES), or null. */
7
+ export declare function sniffFileType(bytes: Uint8Array): string | null;
8
+ /** A posted file (a `File` from `FormData`): a Blob with the visitor's file name. */
9
+ type SubmittedFile = Blob & {
10
+ readonly name: string;
11
+ };
12
+ /** What the visitor posted: a multipart body's entries by name (`FormData`). */
13
+ interface SubmittedFiles {
14
+ getAll(name: string): ReadonlyArray<unknown>;
15
+ }
16
+ /** A checked file, as it is sent to Gravity Forms. */
17
+ interface CheckedFile {
18
+ /** The name it is posted under: `input_<field>`, or `input_<field>[]` for several. */
19
+ name: string;
20
+ file: SubmittedFile;
21
+ /** The visitor's file name, without any path or control characters. */
22
+ filename: string;
23
+ /** The format its content proves; never the client's. */
24
+ type: string;
25
+ }
26
+ interface UploadCheck {
27
+ files: CheckedFile[];
28
+ /** Field errors, by field id. */
29
+ errors: Record<number, FormErrorCode>;
30
+ }
31
+ /** The file name without a path, quotes or control characters, keeping its end (the extension). */
32
+ export declare function uploadFilename(name: string): string;
33
+ /**
34
+ * Checks each visible upload field's files: a required field has one, a field
35
+ * takes at most its maximum, and each file has an allowed extension, fits the
36
+ * field's size limit and is what its extension names. Files posted under any
37
+ * other name are ignored. An empty part (no file chosen) isn't a file.
38
+ */
39
+ export declare function validateUploads(descriptor: FormDescriptor, posted: SubmittedFiles): Promise<UploadCheck>;
40
+ //#endregion
3
41
  //#region frontend/src/forms/validate.d.ts
4
42
  /** No single value is longer (characters). */
5
43
  export declare const MAX_VALUE_LENGTH = 10000;
@@ -22,4 +60,4 @@ export declare function validateSubmission(descriptor: FormDescriptor, values: S
22
60
  /** Whether the honeypot was filled in: a bot. Such a submission never reaches the CMS. */
23
61
  export declare function honeypotFilled(descriptor: FormDescriptor, values: SubmittedValues): boolean;
24
62
  //#endregion
25
- export { FORMS_COPY, FORMS_PATH, FORM_DESCRIPTOR_SCHEMA, FORM_STATE_MAX_AGE, FORM_STATE_PARAM, type FormChoice, type FormConfirmation, type FormConsent, type FormDescriptor, type FormDiagnostic, type FormDiagnosticCode, type FormErrorCode, type FormField, type FormFieldType, type FormInput, type FormSpamControl, type FormState, type FormStatus, type FormsCopy, type FormsCopyLanguage, type FormsReadiness, GRAVITY_FORMS_BLOCK, SUPPORTED_FIELD_TYPES, type SubmissionCheck, type SubmittedValues, blockFormId, clearFormStateCookie, findForm, formAction, formDiagnostics, formStateCookie, formStateCookieName, formStateLocation, formsCopyLanguage, formsReadiness, parseFormDescriptor, parseFormDescriptors, readFormState };
63
+ export { type CheckedFile, FORMS_COPY, FORMS_PATH, FORM_DESCRIPTOR_SCHEMA, FORM_STATE_MAX_AGE, FORM_STATE_PARAM, type FormChoice, type FormConfirmation, type FormConsent, type FormDescriptor, type FormDiagnostic, type FormDiagnosticCode, type FormErrorCode, type FormField, type FormFieldType, type FormInput, type FormSpamControl, type FormState, type FormStatus, type FormUpload, type FormsCopy, type FormsCopyLanguage, type FormsReadiness, GRAVITY_FORMS_BLOCK, SUPPORTED_FIELD_TYPES, type SubmissionCheck, type SubmittedFile, type SubmittedFiles, type SubmittedValues, type UploadCheck, VERIFIABLE_FILE_EXTENSIONS, type VerifiableFileExtension, blockFormId, clearFormStateCookie, findForm, formAction, formDiagnostics, formStateCookie, formStateCookieName, formStateLocation, formsCopyLanguage, formsReadiness, hasUploads, largestUpload, parseFormDescriptor, parseFormDescriptors, readFormState };
package/dist/forms.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as blockFormId, c as formDiagnostics, d as parseFormDescriptors, i as SUPPORTED_FIELD_TYPES, l as formsReadiness, n as FORM_DESCRIPTOR_SCHEMA, o as findForm, r as GRAVITY_FORMS_BLOCK, s as formAction, t as FORMS_PATH, u as parseFormDescriptor } from "./descriptor-x8kHFQ79.js";
2
- import { n as honeypotFilled, r as validateSubmission, t as MAX_VALUE_LENGTH } from "./validate-wRgYUNxN.js";
3
- import { a as clearFormStateCookie, c as formStateLocation, i as FORM_STATE_PARAM, l as readFormState, n as formsCopyLanguage, o as formStateCookie, r as FORM_STATE_MAX_AGE, s as formStateCookieName, t as FORMS_COPY } from "./copy-DLto_bWj.js";
4
- export { FORMS_COPY, FORMS_PATH, FORM_DESCRIPTOR_SCHEMA, FORM_STATE_MAX_AGE, FORM_STATE_PARAM, GRAVITY_FORMS_BLOCK, MAX_VALUE_LENGTH, SUPPORTED_FIELD_TYPES, blockFormId, clearFormStateCookie, findForm, formAction, formDiagnostics, formStateCookie, formStateCookieName, formStateLocation, formsCopyLanguage, formsReadiness, honeypotFilled, parseFormDescriptor, parseFormDescriptors, readFormState, validateSubmission };
1
+ import { a as VERIFIABLE_FILE_EXTENSIONS, c as formAction, d as hasUploads, f as largestUpload, i as SUPPORTED_FIELD_TYPES, l as formDiagnostics, m as parseFormDescriptors, n as FORM_DESCRIPTOR_SCHEMA, o as blockFormId, p as parseFormDescriptor, r as GRAVITY_FORMS_BLOCK, s as findForm, t as FORMS_PATH, u as formsReadiness } from "./descriptor-DXmLmjEa.js";
2
+ import { a as sniffFileType, i as FILE_TYPES, n as honeypotFilled, o as uploadFilename, r as validateSubmission, s as validateUploads, t as MAX_VALUE_LENGTH } from "./validate-CqP_F0bk.js";
3
+ import { a as clearFormStateCookie, c as formStateLocation, i as FORM_STATE_PARAM, l as readFormState, n as formsCopyLanguage, o as formStateCookie, r as FORM_STATE_MAX_AGE, s as formStateCookieName, t as FORMS_COPY } from "./copy-CfNr6M73.js";
4
+ export { FILE_TYPES, FORMS_COPY, FORMS_PATH, FORM_DESCRIPTOR_SCHEMA, FORM_STATE_MAX_AGE, FORM_STATE_PARAM, GRAVITY_FORMS_BLOCK, MAX_VALUE_LENGTH, SUPPORTED_FIELD_TYPES, VERIFIABLE_FILE_EXTENSIONS, blockFormId, clearFormStateCookie, findForm, formAction, formDiagnostics, formStateCookie, formStateCookieName, formStateLocation, formsCopyLanguage, formsReadiness, hasUploads, honeypotFilled, largestUpload, parseFormDescriptor, parseFormDescriptors, readFormState, sniffFileType, uploadFilename, validateSubmission, validateUploads };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Delivery, A as SqlDatabase, At as DesignThemeOverrides, B as DesignBlockRenderer, Bt as DesignPreloadedPosts, C as Moved, Ct as PresentationTheme, D as SearchPage, Dt as presentationStyleSheet, E as SearchItem, Et as presentationModel, F as isLanguageHome, Ft as DesignFonts, G as escapeHtml, Gt as designQueryItems, H as DesignRenderContext, Ht as DesignQueryRenderContext, I as languageSubject, It as DesignTextStyles, J as Archives, Jt as renderDesignQuery, K as parseDesignBlocks, Kt as preloadDesignQueryPosts, L as routeLanguage, Lt as ResolvedLogoImage, M as SiteLanguage, Mt as designThemeVariables, N as WordPressLanguages, Nt as resolveDesignTheme, O as SearchQuery, Ot as presentationVariables, P as htmlLang, Pt as resolvePresentationToken, Q as CollectionTerm, R as siteLanguages, Rt as DesignPostContext, S as HomePublication, St as PresentationTargets, T as SearchDelivery, Tt as createPresentationModel, U as DesignRenderOptions, Ut as DesignQueryResolverOptions, V as DesignPattern, Vt as DesignQueryItem, W as designMenuItems, Wt as designPostContext, X as CmsFailureReason, Xt as renderDesignText, Y as CmsFailure, Yt as DesignTextContext, Z as CollectionMembership, Zt as safeDesignRichText, _ as EntryDelivery, _t as designFontsHead, a as ContentExtension, at as Missing, b as HomeDelivery, bt as PresentationBreakpoint, ct as Translation, d as CollectionListing, dt as WordPressMenuItem, et as DesignPresets, f as CollectionPage, ft as WordPressPost, g as DeliveryFailureReason, gt as designFontStyleSheet, h as CollectionTermListing, ht as designFontPreloads, i as ContentConsentConfig, it as HomeContent, j as SqlStatement, jt as designThemeStyleSheet, k as ContentBindings, kt as DesignTheme, l as Alternate, lt as Unavailable, m as CollectionTermCount, mt as DesignTypography, n as CollectionConfig, nt as Extended, o as ContentRuntime, ot as PublishedEntry, p as CollectionQuery, pt as WordPressSettings, q as renderDesignPattern, qt as renderDesignPostBlock, r as ContentConfig, rt as Found, s as ExtensionNode, st as SiteChrome, t as WordPressClient, tt as EntryContent, u as CollectionItem, ut as WordPressImage, v as EntryPage, vt as designTextStyleSheet, w as NotServed, wt as Property, x as HomePage, xt as PresentationModel, y as EntryPublication, yt as Presentation, z as DesignBlock, zt as DesignPostRecord } from "./wordpress-OWJxnbAF.js";
1
+ import { $ as Delivery, A as SqlDatabase, At as DesignThemeOverrides, B as DesignBlockRenderer, Bt as DesignPreloadedPosts, C as Moved, Ct as PresentationTheme, D as SearchPage, Dt as presentationStyleSheet, E as SearchItem, Et as presentationModel, F as isLanguageHome, Ft as DesignFonts, G as escapeHtml, Gt as designQueryItems, H as DesignRenderContext, Ht as DesignQueryRenderContext, I as languageSubject, It as DesignTextStyles, J as Archives, Jt as renderDesignQuery, K as parseDesignBlocks, Kt as preloadDesignQueryPosts, L as routeLanguage, Lt as ResolvedLogoImage, M as SiteLanguage, Mt as designThemeVariables, N as WordPressLanguages, Nt as resolveDesignTheme, O as SearchQuery, Ot as presentationVariables, P as htmlLang, Pt as resolvePresentationToken, Q as CollectionTerm, R as siteLanguages, Rt as DesignPostContext, S as HomePublication, St as PresentationTargets, T as SearchDelivery, Tt as createPresentationModel, U as DesignRenderOptions, Ut as DesignQueryResolverOptions, V as DesignPattern, Vt as DesignQueryItem, W as designMenuItems, Wt as designPostContext, X as CmsFailureReason, Xt as renderDesignText, Y as CmsFailure, Yt as DesignTextContext, Z as CollectionMembership, Zt as safeDesignRichText, _ as EntryDelivery, _t as designFontsHead, a as ContentExtension, at as Missing, b as HomeDelivery, bt as PresentationBreakpoint, ct as Translation, d as CollectionListing, dt as WordPressMenuItem, et as DesignPresets, f as CollectionPage, ft as WordPressPost, g as DeliveryFailureReason, gt as designFontStyleSheet, h as CollectionTermListing, ht as designFontPreloads, i as ContentConsentConfig, it as HomeContent, j as SqlStatement, jt as designThemeStyleSheet, k as ContentBindings, kt as DesignTheme, l as Alternate, lt as Unavailable, m as CollectionTermCount, mt as DesignTypography, n as CollectionConfig, nt as Extended, o as ContentRuntime, ot as PublishedEntry, p as CollectionQuery, pt as WordPressSettings, q as renderDesignPattern, qt as renderDesignPostBlock, r as ContentConfig, rt as Found, s as ExtensionNode, st as SiteChrome, t as WordPressClient, tt as EntryContent, u as CollectionItem, ut as WordPressImage, v as EntryPage, vt as designTextStyleSheet, w as NotServed, wt as Property, x as HomePage, xt as PresentationModel, y as EntryPublication, yt as Presentation, z as DesignBlock, zt as DesignPostRecord } from "./wordpress-CZ4JCdXF.js";
2
2
  //#region frontend/src/cover-video-html.d.ts
3
3
  /** Native decorative Cover video must not start before motion preferences are
4
4
  * known. Retain every source, poster, child and independent video attribute. */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as VIDEO_HERO_BLOCK, A as formatDate, B as renderDesignPostBlock, C as siteLanguages, D as renderDesignPattern, E as parseDesignBlocks, F as uriPath, G as renderWordPressBlocks, H as hasDesignCarousel, I as renderDesignCover, J as DESIGN_CAROUSEL_ITEM, K as designGridStyle, L as designPostContext, M as responseStatus, N as routeOf, O as cmsTime, P as stripHtml, Q as renderDesignCarouselItem, R as designQueryItems, S as routeLanguage, T as escapeHtml, U as hasVideoHero, V as renderDesignQuery, W as parseWordPressBlocks, X as designCarouselStyle, Y as DESIGN_CAROUSEL_NAVIGATION, Z as renderDesignCarousel, _t as selectSiteLogo, a as designTextStyleSheet, at as enhanceDesignNavigationHtml, b as isLanguageHome, ct as presentationHtml, dt as presentationStyleSheet, et as renderDesignVideoHero, ft as presentationVariables, gt as resolvePresentationToken, ht as resolveDesignTheme, it as safeDesignRichText, j as menuItemHref, k as decodeEntities, lt as createPresentationModel, mt as designThemeVariables, n as designFontStyleSheet, nt as renderDesignImage, ot as navigationStyleSheet, pt as designThemeStyleSheet, q as DESIGN_CAROUSEL, r as designFontsHead, rt as renderDesignText, st as renderDesignNavigation, t as designFontPreloads, tt as youtubeVideoId, ut as presentationModel, w as designMenuItems, x as languageSubject, y as htmlLang, yt as enhanceDesignCoverVideosHtml, z as preloadDesignQueryPosts } from "./design-fonts-B0ts54vY.js";
1
+ import { $ as VIDEO_HERO_BLOCK, A as formatDate, B as renderDesignPostBlock, C as siteLanguages, D as renderDesignPattern, E as parseDesignBlocks, F as uriPath, G as renderWordPressBlocks, H as hasDesignCarousel, I as renderDesignCover, J as DESIGN_CAROUSEL_ITEM, K as designGridStyle, L as designPostContext, M as responseStatus, N as routeOf, O as cmsTime, P as stripHtml, Q as renderDesignCarouselItem, R as designQueryItems, S as routeLanguage, T as escapeHtml, U as hasVideoHero, V as renderDesignQuery, W as parseWordPressBlocks, X as designCarouselStyle, Y as DESIGN_CAROUSEL_NAVIGATION, Z as renderDesignCarousel, _t as selectSiteLogo, a as designTextStyleSheet, at as enhanceDesignNavigationHtml, b as isLanguageHome, ct as presentationHtml, dt as presentationStyleSheet, et as renderDesignVideoHero, ft as presentationVariables, gt as resolvePresentationToken, ht as resolveDesignTheme, it as safeDesignRichText, j as menuItemHref, k as decodeEntities, lt as createPresentationModel, mt as designThemeVariables, n as designFontStyleSheet, nt as renderDesignImage, ot as navigationStyleSheet, pt as designThemeStyleSheet, q as DESIGN_CAROUSEL, r as designFontsHead, rt as renderDesignText, st as renderDesignNavigation, t as designFontPreloads, tt as youtubeVideoId, ut as presentationModel, w as designMenuItems, x as languageSubject, y as htmlLang, yt as enhanceDesignCoverVideosHtml, z as preloadDesignQueryPosts } from "./design-fonts-tbvMfxXU.js";
2
2
  import { n as videoHeroStyleSheet, t as videoHeroFrameGeometry } from "./video-hero-D8AcMR4q.js";
3
3
  //#region frontend/src/wp-block-styles.ts
4
4
  const validSlug = (slug) => /^[a-z0-9][a-z0-9-]*$/.test(slug);
package/dist/server.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ContentExtension, c as createContentRuntime, i as ContentConsentConfig, k as ContentBindings, n as CollectionConfig, o as ContentRuntime, r as ContentConfig, s as ExtensionNode, t as WordPressClient } from "./wordpress-OWJxnbAF.js";
1
+ import { a as ContentExtension, c as createContentRuntime, i as ContentConsentConfig, k as ContentBindings, n as CollectionConfig, o as ContentRuntime, r as ContentConfig, s as ExtensionNode, t as WordPressClient } from "./wordpress-CZ4JCdXF.js";
2
2
  export { CollectionConfig, type ContentBindings, ContentConfig, ContentConsentConfig, ContentExtension, ContentRuntime, ExtensionNode, type WordPressClient, createContentRuntime };
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as parseFragment } from "./dist-5jNKLmbn.js";
2
- import { C as siteLanguages, D as renderDesignPattern, E as parseDesignBlocks, F as uriPath, G as renderWordPressBlocks, H as hasDesignCarousel, N as routeOf, O as cmsTime, P as stripHtml, S as routeLanguage, U as hasVideoHero, W as parseWordPressBlocks, _ as unionType, at as enhanceDesignNavigationHtml, b as isLanguageHome, bt as safeContentUrl, c as arrayType, d as lazyType, f as literalType, g as stringType, h as recordType, ht as resolveDesignTheme, i as designFontsSchema, j as menuItemHref, k as decodeEntities, l as booleanType, m as objectType, o as designTextStylesSchema, p as numberType, s as NEVER, u as enumType, v as unknownType, vt as assertDesignBlocks, x as languageSubject, yt as enhanceDesignCoverVideosHtml, z as preloadDesignQueryPosts } from "./design-fonts-B0ts54vY.js";
3
- import { d as parseFormDescriptors, o as findForm } from "./descriptor-x8kHFQ79.js";
2
+ import { C as siteLanguages, D as renderDesignPattern, E as parseDesignBlocks, F as uriPath, G as renderWordPressBlocks, H as hasDesignCarousel, N as routeOf, O as cmsTime, P as stripHtml, S as routeLanguage, U as hasVideoHero, W as parseWordPressBlocks, _ as unionType, at as enhanceDesignNavigationHtml, b as isLanguageHome, bt as safeContentUrl, c as arrayType, d as lazyType, f as literalType, g as stringType, h as recordType, ht as resolveDesignTheme, i as designFontsSchema, j as menuItemHref, k as decodeEntities, l as booleanType, m as objectType, o as designTextStylesSchema, p as numberType, s as NEVER, u as enumType, v as unknownType, vt as assertDesignBlocks, x as languageSubject, yt as enhanceDesignCoverVideosHtml, z as preloadDesignQueryPosts } from "./design-fonts-tbvMfxXU.js";
3
+ import { m as parseFormDescriptors, s as findForm } from "./descriptor-DXmLmjEa.js";
4
4
  import { t as bearerMatches } from "./bearer-DMOCoSci.js";
5
5
  import { _ as consentConfiguration } from "./state-BYWc5TUB.js";
6
6
  import { i as rewriteEmbeds, l as parseLegalLanguage } from "./embeds-CJJemAmE.js";
@@ -1,6 +1,6 @@
1
1
  //#region frontend/src/forms/copy.d.ts
2
2
  type FormsCopyLanguage = "en" | "pt-PT";
3
- type FormErrorCode = "required" | "email" | "email-mismatch" | "number" | "range" | "url" | "choice" | "too-long";
3
+ type FormErrorCode = "required" | "email" | "email-mismatch" | "number" | "range" | "url" | "choice" | "too-long" | "file-type" | "file-size" | "file-count";
4
4
  interface FormsCopy {
5
5
  required: string;
6
6
  requiredFields: string;
@@ -20,13 +20,23 @@ interface FormsCopy {
20
20
  honeypot: string;
21
21
  /** With Turnstile and no JavaScript. */
22
22
  needsJavaScript: string;
23
+ /** An upload field's hint: `{types}` is the list of extensions. */
24
+ fileTypes: string;
25
+ /** `{size}` is the per-file limit (5 MB). */
26
+ fileSize: string;
27
+ /** `{count}` is the most files a field takes, when more than one. */
28
+ fileCount: string;
29
+ /** After a post that came back: browsers can't keep chosen files. */
30
+ filesAgain: string;
31
+ /** The whole post was larger than the Site accepts. */
32
+ tooLarge: string;
23
33
  }
24
34
  declare const FORMS_COPY: Record<FormsCopyLanguage, FormsCopy>;
25
35
  /** The copy language for a page's `lang`: Portuguese for `pt*`, else English. */
26
36
  declare function formsCopyLanguage(language: string | null | undefined): FormsCopyLanguage;
27
37
  //#endregion
28
38
  //#region frontend/src/forms/state.d.ts
29
- type FormStatus = "sent" | "invalid" | "failed" | "verification" | "too-many";
39
+ type FormStatus = "sent" | "invalid" | "failed" | "verification" | "too-many" | "too-large";
30
40
  interface FormState {
31
41
  formId: number;
32
42
  status: FormStatus;
@@ -1,3 +1,110 @@
1
+ //#region frontend/src/forms/uploads.ts
2
+ /** The format each allowed extension's content must prove, as a MIME type. */
3
+ const FILE_TYPES = {
4
+ jpg: "image/jpeg",
5
+ jpeg: "image/jpeg",
6
+ png: "image/png",
7
+ gif: "image/gif",
8
+ webp: "image/webp",
9
+ pdf: "application/pdf"
10
+ };
11
+ /** Bytes read from the start of a file to identify it. */
12
+ const SIGNATURE_BYTES = 12;
13
+ const ascii = (text) => new TextEncoder().encode(text);
14
+ const JPEG = [
15
+ 255,
16
+ 216,
17
+ 255
18
+ ];
19
+ const PNG = [
20
+ 137,
21
+ 80,
22
+ 78,
23
+ 71,
24
+ 13,
25
+ 10,
26
+ 26,
27
+ 10
28
+ ];
29
+ const GIF87 = ascii("GIF87a");
30
+ const GIF89 = ascii("GIF89a");
31
+ const RIFF = ascii("RIFF");
32
+ const WEBP = ascii("WEBP");
33
+ const PDF = ascii("%PDF-");
34
+ function startsWith(bytes, offset, expected) {
35
+ for (let index = 0; index < expected.length; index++) if (bytes[offset + index] !== expected[index]) return false;
36
+ return true;
37
+ }
38
+ /** The format a file's first bytes prove (a MIME type of FILE_TYPES), or null. */
39
+ function sniffFileType(bytes) {
40
+ if (startsWith(bytes, 0, JPEG)) return "image/jpeg";
41
+ if (startsWith(bytes, 0, PNG)) return "image/png";
42
+ if (startsWith(bytes, 0, GIF87) || startsWith(bytes, 0, GIF89)) return "image/gif";
43
+ if (startsWith(bytes, 0, RIFF) && startsWith(bytes, 8, WEBP)) return "image/webp";
44
+ if (startsWith(bytes, 0, PDF)) return "application/pdf";
45
+ return null;
46
+ }
47
+ const isFile = (value) => value instanceof Blob && typeof value.name === "string";
48
+ /** The file name without a path, quotes or control characters, keeping its end (the extension). */
49
+ function uploadFilename(name) {
50
+ const base = name.split(/[\\/]/).pop() ?? "";
51
+ return Array.from(base.replace(/[\u0000-\u001f\u007f"]/g, "").trim()).slice(-200).join("");
52
+ }
53
+ const extensionOf = (filename) => /\.([^.]+)$/.exec(filename)?.[1]?.toLowerCase() ?? "";
54
+ /**
55
+ * Checks each visible upload field's files: a required field has one, a field
56
+ * takes at most its maximum, and each file has an allowed extension, fits the
57
+ * field's size limit and is what its extension names. Files posted under any
58
+ * other name are ignored. An empty part (no file chosen) isn't a file.
59
+ */
60
+ async function validateUploads(descriptor, posted) {
61
+ const files = [];
62
+ const errors = {};
63
+ for (const field of descriptor.fields) {
64
+ const input = field.inputs[0];
65
+ const upload = field.upload;
66
+ if (field.type !== "fileupload" || field.visibility !== "visible" || !input || !upload) continue;
67
+ const chosen = posted.getAll(input.name).filter(isFile).filter((file) => file.size > 0 || file.name !== "");
68
+ if (chosen.length === 0) {
69
+ if (field.required) errors[field.id] = "required";
70
+ continue;
71
+ }
72
+ if (chosen.length > Math.max(upload.maxFiles, 1)) {
73
+ errors[field.id] = "file-count";
74
+ continue;
75
+ }
76
+ const checked = [];
77
+ for (const file of chosen) {
78
+ const filename = uploadFilename(file.name);
79
+ const extension = extensionOf(filename);
80
+ const expected = upload.allowedExtensions.includes(extension) ? FILE_TYPES[extension] : void 0;
81
+ if (!expected || file.size === 0) {
82
+ errors[field.id] = "file-type";
83
+ break;
84
+ }
85
+ if (file.size > upload.maxFileSize) {
86
+ errors[field.id] = "file-size";
87
+ break;
88
+ }
89
+ if (sniffFileType(new Uint8Array(await file.slice(0, SIGNATURE_BYTES).arrayBuffer())) !== expected) {
90
+ errors[field.id] = "file-type";
91
+ break;
92
+ }
93
+ checked.push({
94
+ name: upload.multipleFiles ? `${input.name}[]` : input.name,
95
+ file,
96
+ filename,
97
+ type: expected
98
+ });
99
+ }
100
+ if (!errors[field.id]) files.push(...checked);
101
+ }
102
+ return {
103
+ files,
104
+ errors
105
+ };
106
+ }
107
+ //#endregion
1
108
  //#region frontend/src/forms/validate.ts
2
109
  /** No single value is longer (characters). */
3
110
  const MAX_VALUE_LENGTH = 1e4;
@@ -38,7 +145,8 @@ function check(field, values, input, shown) {
38
145
  const first = field.inputs[0];
39
146
  switch (field.type) {
40
147
  case "html":
41
- case "section": return null;
148
+ case "section":
149
+ case "fileupload": return null;
42
150
  case "hidden": {
43
151
  if (!first) return null;
44
152
  const value = values.get(first.name) === null ? field.defaultValue ?? "" : take(first.name);
@@ -127,4 +235,4 @@ function honeypotFilled(descriptor, values) {
127
235
  return descriptor.honeypot !== null && (values.get(descriptor.honeypot.name) ?? "") !== "";
128
236
  }
129
237
  //#endregion
130
- export { honeypotFilled as n, validateSubmission as r, MAX_VALUE_LENGTH as t };
238
+ export { sniffFileType as a, FILE_TYPES as i, honeypotFilled as n, uploadFilename as o, validateSubmission as r, validateUploads as s, MAX_VALUE_LENGTH as t };
@@ -1,6 +1,6 @@
1
1
  //#endregion
2
2
  //#region frontend/src/consent/version.ts
3
3
  /** The UI/adapter version recorded with each decision (CN-R7). */
4
- const CONSENT_UI_VERSION = `gq-content@0.5.0`;
4
+ const CONSENT_UI_VERSION = `gq-content@0.6.0`;
5
5
  //#endregion
6
6
  export { CONSENT_UI_VERSION as t };
@@ -1,6 +1,6 @@
1
1
  import { r as ConsentConfiguration, t as AnalyticsSettings } from "./configuration-C_JGygQI.js";
2
2
  import { r as LegalLanguage } from "./legal-C5Xiq59W.js";
3
- import { o as FormDescriptor } from "./descriptor-B-e7gHi1.js";
3
+ import { o as FormDescriptor } from "./descriptor-BF5Y9a1J.js";
4
4
  //#endregion
5
5
  //#region frontend/src/design-text.d.ts
6
6
  /** Already loaded, authorized values. No lookup, fetching or query selection.
@@ -313,9 +313,9 @@ interface DesignTypography {
313
313
  /**
314
314
  * The `@font-face` rules (Site faces with `font-display` as delivered, and the
315
315
  * metric-matched fallback faces) and the assigned slots' `--gqd-font-*` stacks.
316
- * The stacks are set on `:root` and again on the content root: the theme
317
- * stylesheet declares the system stacks on that root, which would otherwise
318
- * shadow the assignment inside Design content.
316
+ * GQ Design sets the stacks on `:root` and, with doubled specificity, on the
317
+ * content root: the theme stylesheet declares the system stacks on that root,
318
+ * which would otherwise shadow the assignment inside Design content.
319
319
  */
320
320
  declare function designFontStyleSheet(fonts?: DesignFonts | null, root?: string): string;
321
321
  /** At most two WOFF2 preloads GQ Design marks (the text styles' primary Latin faces). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getquick/content",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "UNLICENSED",
5
5
  "files": [
6
6
  "dist",