Skip to content

feat(schema): multiple option for image/file fields (ordered media galleries) - #1

Closed
Rimander wants to merge 1 commit into
mainfrom
feat/media-field-multiple
Closed

feat(schema): multiple option for image/file fields (ordered media galleries)#1
Rimander wants to merge 1 commit into
mainfrom
feat/media-field-multiple

Conversation

@Rimander

Copy link
Copy Markdown
Owner

What does this PR do?

Adds a multiple option (stored as validation.multiple) to image and file fields so editors can manage an ordered set of media on one entry — photo galleries, slideshows, download lists — instead of a single item. This is the "multiple: true modifier on existing image/file fields" shape requested for gallery-style content, chosen over a new gallery field type: the column type is unchanged (TEXT storing JSON), so existing fields can be upgraded in place with no migration, and the flag composes with allowedMimeTypes, minItems/maxItems.

Core:

  • FieldValidation.multiple + API zod acceptance (fieldValidation), so the flag survives create/update field calls.
  • Zod generator wraps the image/file item schema in z.array() when multiple is set, applying minItems/maxItems. Toggle round-trips are safe both ways: a stored single object validates (wrapped into a one-item array) after enabling multiple, and a one-element array unwraps after disabling it (Auto save validation errors emdash-cms/emdash#867-class autosave protection).
  • Save-side MIME allowlist validation checks every item of the array (previously arrays were silently skipped).
  • Media-usage extraction records one occurrence per item with indexed field paths (gallery[0], gallery[1], …).
  • normalizeMediaFields enriches each array item from the media provider.
  • Generated TypeScript types emit Array<{...}> for multiple fields.

Admin:

  • Field editor gains an "Allow multiple images/files" switch with Min/Max Items, plus a warning when disabling it on an existing field.
  • MediaPickerModal gains an additive multi-select mode (multiple + onSelectMany); single-select behavior is untouched.
  • New MultiMediaFieldRenderer renders the ordered list with drag-and-drop reordering (same dnd-kit pattern as RepeaterField) for both image and file fields.

Also fixes a corrupt Arabic translation ({0, plural, one {(# item)} other {(# items)}} had unbalanced ICU braces) that crashed any component rendering that count badge in Arabic — including the pre-existing RepeaterField. This is a surgical one-line repair of a broken catalog entry, not extraction churn; without it the new gallery renderer (which reuses the same msgid) crashes for Arabic users. Reproduced and verified with @lingui/core directly.

Closes #

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change) — full core suite (4690 tests) and admin suite (1100 tests)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). The ar/messages.po diff is a deliberate one-line fix of a corrupt ICU plural entry (see description), not extracted catalog churn.
  • I have added a changeset
  • New features link to an approved Discussion: to be filled in before submitting upstream — this PR is on the fork; the feature responds to a downstream gallery/multi-media request (no upstream Discussion link yet)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Code + Claude Fable 5

Screenshots / test output

Verified end-to-end in the demos/simple dev app (Node + SQLite):

  • Created an image field with "Allow multiple" in the field editor; validation persisted as {"multiple": true} (type stays image, column stays TEXT).
  • Selected 3 images at once in the media picker, saw ordered thumbnail rows, drag-reordered, removed one; each state autosaved and the API returned the gallery as an ordered array reflecting every change.
  • Generated demo types picked up gallery?: Array<{ id: string; ... }>.
  • Tested the editor in Arabic (RTL): rows mirror correctly (thumbnail right, remove button left) and the item-count badge renders Arabic plurals after the catalog fix.

🤖 Generated with Claude Code

…lleries)

Adds validation.multiple to image/file fields: the value becomes an
ordered array of media values stored in the existing TEXT column, with
zod array validation (single<->array round-trip preprocess), per-item
MIME allowlist enforcement, media-usage extraction with indexed paths,
and runtime normalization per item. The admin gains an "Allow multiple"
toggle in the field editor, a multi-select mode in the media picker,
and a sortable gallery renderer for image/file fields.

Also repairs a corrupt Arabic plural translation (unbalanced ICU braces
for "(# item)/(# items)") that crashed any component rendering that
count badge in Arabic, including the existing RepeaterField.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Scope check

This PR changes 1,048 lines across 17 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.

@github-actions

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@github-actions

Copy link
Copy Markdown

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
packages/admin/src/locales/ar/messages.po Localization changed, will be marked as complete.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@Rimander Rimander closed this Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant