diff --git a/.changeset/avif-media-uploads.md b/.changeset/avif-media-uploads.md new file mode 100644 index 0000000000..3564cb8fbd --- /dev/null +++ b/.changeset/avif-media-uploads.md @@ -0,0 +1,10 @@ +--- +"emdash": patch +"@emdash-cms/admin": patch +--- + +Fixes AVIF images being rejected with "File type not allowed" on upload. `image/avif` is back in the default media allowlist alongside PNG, JPEG, GIF, and WebP, so editors can upload `.avif` files again from the media library and from image fields that use the default allowlist. + +The admin file picker now offers `.avif` files and renders their thumbnails, the built-in "Images" preset in a field's allowed-types editor includes AVIF, and `.avif` works as extension shorthand in a field's `allowedMimeTypes`. + +SVG stays excluded from the default allowlist. diff --git a/docs/src/content/docs/guides/media-library.mdx b/docs/src/content/docs/guides/media-library.mdx index 77f2189c10..ab7e5f5537 100644 --- a/docs/src/content/docs/guides/media-library.mdx +++ b/docs/src/content/docs/guides/media-library.mdx @@ -40,7 +40,7 @@ EmDash accepts these file types by default: | Category | Extensions | | --------- | ----------------------------------------------- | -| Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp` | +| Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.avif` | | Documents | `.pdf` | | Video | `.mp4`, `.webm`, `.mov` | | Audio | `.mp3`, `.wav`, `.ogg` | diff --git a/packages/admin/src/components/AllowedTypesEditor.tsx b/packages/admin/src/components/AllowedTypesEditor.tsx index 577c8b5279..9822265e01 100644 --- a/packages/admin/src/components/AllowedTypesEditor.tsx +++ b/packages/admin/src/components/AllowedTypesEditor.tsx @@ -12,7 +12,10 @@ interface Preset { } const PRESETS: ReadonlyArray = [ - { key: "images", mimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"] }, + { + key: "images", + mimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"], + }, { key: "pdf", mimeTypes: ["application/pdf"] }, { key: "documents", diff --git a/packages/admin/src/components/MediaUploadDialog.tsx b/packages/admin/src/components/MediaUploadDialog.tsx index f9aa445d5d..a5e5242afc 100644 --- a/packages/admin/src/components/MediaUploadDialog.tsx +++ b/packages/admin/src/components/MediaUploadDialog.tsx @@ -19,13 +19,19 @@ import * as React from "react"; import { formatFileSize } from "../lib/media-utils.js"; export const LOCAL_MEDIA_UPLOAD_ACCEPT = - "image/png,image/jpeg,image/gif,image/webp,video/*,audio/*,application/pdf"; + "image/png,image/jpeg,image/gif,image/webp,image/avif,video/*,audio/*,application/pdf"; const DEFAULT_CONCURRENCY = 3; const MAX_CONCURRENCY = 6; const MAX_VISIBLE_ROWS = 100; const MAX_PREVIEW_BYTES = 8 * 1024 * 1024; -const PREVIEW_MIME_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]); +const PREVIEW_MIME_TYPES = new Set([ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/avif", +]); type UploadStatus = "queued" | "uploading" | "complete" | "failed"; diff --git a/packages/admin/src/lib/mime-utils.ts b/packages/admin/src/lib/mime-utils.ts index 96fdae0ca1..d3e714dc38 100644 --- a/packages/admin/src/lib/mime-utils.ts +++ b/packages/admin/src/lib/mime-utils.ts @@ -5,6 +5,7 @@ export const EXTENSION_TO_MIME: Readonly> = { ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", + ".avif": "image/avif", ".svg": "image/svg+xml", ".mp3": "audio/mpeg", ".wav": "audio/wav", diff --git a/packages/core/src/api/handlers/media-allowlist.ts b/packages/core/src/api/handlers/media-allowlist.ts index 9c7c06f3f0..0c049ac362 100644 --- a/packages/core/src/api/handlers/media-allowlist.ts +++ b/packages/core/src/api/handlers/media-allowlist.ts @@ -21,6 +21,7 @@ export const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [ "image/jpeg", "image/gif", "image/webp", + "image/avif", "video/", "audio/", "application/pdf", diff --git a/packages/core/src/media/mime.ts b/packages/core/src/media/mime.ts index 495e9f62c5..aedb8a8678 100644 --- a/packages/core/src/media/mime.ts +++ b/packages/core/src/media/mime.ts @@ -23,6 +23,7 @@ export const EXTENSION_TO_MIME: Readonly> = { ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", + ".avif": "image/avif", ".svg": "image/svg+xml", ".mp3": "audio/mpeg", ".wav": "audio/wav", diff --git a/packages/core/tests/unit/media/media-allowlist.test.ts b/packages/core/tests/unit/media/media-allowlist.test.ts index 7f7a9243c3..65957ca6c6 100644 --- a/packages/core/tests/unit/media/media-allowlist.test.ts +++ b/packages/core/tests/unit/media/media-allowlist.test.ts @@ -15,6 +15,10 @@ describe("GLOBAL_UPLOAD_ALLOWLIST", () => { expect(matchesMimeAllowlist("image/webp", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); }); + it("allows image/avif, which the media routes already serve inline", () => { + expect(matchesMimeAllowlist("image/avif", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); + }); + it("still allows video, audio, and pdf", () => { expect(matchesMimeAllowlist("video/mp4", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); expect(matchesMimeAllowlist("audio/mpeg", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); diff --git a/packages/core/tests/unit/media/mime.test.ts b/packages/core/tests/unit/media/mime.test.ts index 4fd247a92f..71c6d3afc1 100644 --- a/packages/core/tests/unit/media/mime.test.ts +++ b/packages/core/tests/unit/media/mime.test.ts @@ -76,6 +76,7 @@ describe("expandExtensionShorthand", () => { it("expands known dot-extensions", () => { expect(expandExtensionShorthand(".pdf")).toBe("application/pdf"); expect(expandExtensionShorthand(".PDF")).toBe("application/pdf"); + expect(expandExtensionShorthand(".avif")).toBe("image/avif"); expect(expandExtensionShorthand(".docx")).toBe( "application/vnd.openxmlformats-officedocument.wordprocessingml.document", );