diff --git a/docs/decap-cms-pilot.md b/docs/decap-cms-pilot.md index d726b9f..dc2804e 100644 --- a/docs/decap-cms-pilot.md +++ b/docs/decap-cms-pilot.md @@ -156,6 +156,12 @@ not accept executables, archives, HTML, scripts, or SVG uploads. Asset rights, alt text, crop, dimensions, and optimization remain operator review items even when the CMS accepts an upload. +Decap's image widget applies its field `pattern` to the selected file's base +name, not to the repository-relative media path. Keep that UI pattern limited +to the approved raster filename extensions; enforce the full +`public/images/uploads` path separately in the schema, verifier, and Gitea +branch rules below. + The media gallery lists only `public/images/uploads`, so the pilot seeds that directory with the images referenced by CMS-controlled fields. Removing an image from content clears the field and uses the page's text-only or initials diff --git a/public/admin/config.yml b/public/admin/config.yml index e0c4af4..871ea60 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -144,7 +144,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: max_file_size: 512000 @@ -181,7 +181,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: Home hero image description @@ -194,7 +194,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: Home story image description @@ -207,7 +207,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: About hero image description @@ -220,7 +220,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: About story image description @@ -233,7 +233,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: Services hero image description @@ -246,7 +246,7 @@ collections: required: false choose_url: false allow_multiple: false - pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] + pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.'] media_library: config: { max_file_size: 512000, multiple: false } - label: Contact hero image description diff --git a/scripts/verify-decap.mjs b/scripts/verify-decap.mjs index b7681b2..2b9a863 100644 --- a/scripts/verify-decap.mjs +++ b/scripts/verify-decap.mjs @@ -45,7 +45,7 @@ const markdownField = { editor_components: [], }; const cmsImagePattern = [ - '^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\\.(?:jpe?g|png|webp|avif)$', + '^[A-Za-z0-9][A-Za-z0-9._-]*\\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.', ]; const optionalImageField = { @@ -160,6 +160,8 @@ allFields.forEach(visitField); const imageFields = allFields.filter(({ widget }) => widget === 'image'); assert(imageFields.length === 7, 'Only the team image and six fixed page-image fields may expose media selection.'); for (const field of imageFields) expectSubset(field, optionalImageField, `image field ${field.name}`); +assert(new RegExp(cmsImagePattern[0]).test('example-photo.jpg'), 'Image widget validation must accept the filename Decap passes to field patterns.'); +assert(!new RegExp(cmsImagePattern[0]).test('example-photo.svg'), 'Image widget validation must reject SVG filenames.'); const schema = await readFile('src/content.config.ts', 'utf8'); for (const fragment of [