feat(admin,core): image sub-fields in repeaters with media picker#1425
Conversation
Repeater rows rendered non-scalar sub-fields as plain text inputs. Adds case 'image' to the sub-field renderer (reusing ImageFieldRenderer, extracted from ContentEditor to avoid a circular import), the builder type option, and extends REPEATER_SUB_FIELD_TYPES + the API Zod enum (which was also missing the documented 'url' type). Closes emdash-cms#1424
🦋 Changeset detectedLatest commit: d76c937 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scope checkThis PR changes 542 lines across 9 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
There was a problem hiding this comment.
This is a well-scoped, clean implementation of image sub-fields inside repeaters.
Approach — The change solves the right problem (#1424): repeater rows previously forced every non-scalar sub-field through a plain text input, which made photo galleries painful. Extracting ImageFieldRenderer into its own module to avoid a circular import (ContentEditor → RepeaterField → ImageFieldRenderer) is the correct architectural move, and the component is moved verbatim with no behavior change to top-level image fields.
What I checked
- Diff + full context of all changed files (
ContentEditor,RepeaterField,FieldEditor,ImageFieldRenderer, core schema/types, tests, changeset). - Traced
ImageFieldRendererextraction against the original inline code — verbatim. - Verified
REPEATER_SUB_FIELD_TYPES, the Zod enum increateFieldBody, and the schema-builder select stay in sync and now correctly include bothimageand the previously missingurl. - Checked AGENTS.md conventions: all new UI strings are Lingui-wrapped, Tailwind classes are RTL-safe (logical
end-*,ms-*), no SQL or content-table changes, changeset is present and correctly versioned. - Confirmed repeater item values still flow through the existing
z.unknown()path in the Zod generator — no new validation surface was needed. - Reviewed test coverage: targeted component tests exercise rendering the picker, showing an existing media-value preview, and initializing new items with
null; core tests iterate all whitelisted sub-field types and reject unsupported ones.
Headline conclusion — No bugs, no regressions, no convention violations. The PR is ready to land.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
|
Heads-up on the red Smoke Tests check: it fails in |
What does this PR do?
Repeater rows previously rendered every non-scalar sub-field as a plain text input, so common shapes like photo galleries had to be built from hand-pasted URLs (full context in #1424). This adds first-class
imagesub-fields:RepeaterField's sub-field switch gainscase "image"and renders the same media-picker UI as top-level image fields (select / preview / change / remove), storing the same MediaValue shape; legacy string URLs still display via the existing backwards-compat path. New items initialize image sub-fields asnull(instead of"").ImageFieldRenderer(+ itsImageFieldValueshape) moved verbatim fromContentEditor.tsxinto its own module;ContentEditorimportsRepeaterField, so importing back fromContentEditorwould have been circular. No behavior change to top-level image fields.Image(Lingui-wrapped like its siblings).REPEATER_SUB_FIELD_TYPES/RepeaterSubField.typeand the API Zod enum gainimage. The Zod enum also gains the previously missingurlentry — the builder UI andREPEATER_SUB_FIELD_TYPESalready offered/documented it, butcreateFieldBodyrejected it.No new value-validation path is needed: repeater item values go through the existing
z.unknown()repeater handling in the zod generator, identical to before.Closes #1424
Type of change
Checklist
pnpm typecheckpasses (packages/admin + packages/core)pnpm lintpasses (lint:quickclean)pnpm testpasses (targeted:admin tests/components/RepeaterField.test.tsx5/5 incl. 2 pre-existing datetime tests,FieldEditor.test.tsx36/36, corefield-validation-schema.test.ts10/10; new tests red without the change)pnpm formathas been runImageselect label uses the Linguitmacro; nomessages.pochanges included per CONTRIBUTING)@emdash-cms/adminminor,emdashminor)AI-generated code disclosure
Screenshots / test output