Repository navigation
feat(gallery): photo credits on galleries and carousels - #155
Merged
Merged
Conversation
Photo galleries and carousels carried no credit at all, and the `caption`
field their blocks already declared was never rendered. Both blocks now take
a `credit` relationship, mirroring the one the inline `upload` node has had
since 20260322 ("Overrides the photographer set on the media record").
Credit resolves per photo in the same order the upload node uses: an explicit
block credit, else the media record's `photographer`, else its
`writeInPhotographer` free text. `credit` is hasMany, so a co-shot frame
credits everyone at once on one line — "A, B, C/The Polytechnic" — with a
single suffix and each staff name linking to its profile. Write-ins are
rendered bare, since they are not staff.
The two blocks diverge in how they present it:
carousel caption + credit beneath the frame, muted, unchanged styling
photo gallery credit only, laid over the photo; no captions
Galleries no longer take a caption at all — the field is gone from the block,
so it cannot be entered. PhotoGallery also strips `caption` before rendering
rather than merely ignoring it, so text saved before the field was removed
cannot resurface, and the area sampled below matches exactly what is drawn.
Captions already stored in published gallery blocks stop rendering, and
Payload drops them the next time an editor saves that article.
Because a gallery credit sits on the photograph, lib/imageLuminance.ts picks
both where it goes and what colour it is from the pixels it will actually
cover. It reconstructs the caption's own box — the same 12px/18px/px-4 pt-2
pb-3 geometry the component renders with, wrapped to an estimated line count —
in each bottom corner, then samples only those two rectangles. The corner with
the lower luminance standard deviation wins, because text over an even area
stays legible where the same text over high-contrast detail does not, whatever
colour it is. Left takes ties by a 3-point margin so near-identical photos in
one gallery do not flip corners. The winning corner's mean luminance then
picks white or black, with a matching text-shadow to hold an edge.
Sampling only the text's own rectangle changes the answer in practice: the
playhouse frame averages 129 over the whole image but 212 in the corner the
credit lands in, so it is the one frame that reads black.
Column width is viewport-relative, so sampling assumes a 1280px reference.
Narrower viewports wrap to more lines and cover more of the photo, which only
ever makes the measured rectangle a subset of the real one. Legacy-archive
rows have no local file to sample and fall back to white, bottom-left.
No migration accompanies this. Lexical block fields serialize into the
existing jsonb richText columns — there are no photo_gallery or carousel
tables, and no new column — verified by applying migrations/ to a fresh
database with PAYLOAD_DISABLE_PUSH=1 and NODE_ENV=production, then writing and
reading back a multi-credit gallery. `pnpm generate:types` is likewise a no-op,
since lexical blocks do not surface in payload-types.ts.
Both block definitions are mirrored in payload.config.ts and
collections/LiveArticles.ts, so live-article updates get the same treatment;
they already render through the same SerializeLexical path.
pnpm typecheck clean. pnpm lint 0 errors (53 pre-existing migrations/
warnings, unchanged).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Photo galleries and carousels carried no credit at all, and the
captionfield their blocks already declared was never rendered. Both blocks now take acreditrelationship, mirroring the one the inlineuploadnode has had since20260322— "Overrides the photographer set on the media record."Credit resolves per photo in the same order the upload node uses: an explicit block credit → the media record's
photographer→ itswriteInPhotographerfree text.creditishasMany, so a co-shot frame credits everyone at once on one line — "A, B, C/The Polytechnic" — with a single suffix, each staff name linking to its profile. Write-ins render bare, since they aren't staff.The two blocks diverge
Galleries no longer take a caption at all; the field is gone from the block, so it can't be entered.
Important
Captions already stored in published gallery blocks stop rendering, and Payload drops them the next time an editor saves that article.
PhotoGallerystripscaptionrather than merely ignoring it, so text saved before the field was removed can't resurface, and the sampled area matches exactly what's drawn.Placing a credit on a photograph
Because a gallery credit sits on the image,
lib/imageLuminance.tsdecides both where it goes and what colour it is from the pixels it will actually cover.It reconstructs the caption's own box — the same
12px/18px/px-4 pt-2 pb-3geometry the component renders with, wrapped to an estimated line count — in each bottom corner, and samples only those two rectangles.text-shadowto hold an edge.Measured on seed images at 3 columns:
Sampling the text's own rectangle rather than the whole image changes the answer in practice: playhouse averages 129 across the frame but 212 in the corner the credit lands in, so it's the one that reads black.
Column width is viewport-relative, so sampling assumes a 1280px reference. Narrower viewports wrap to more lines and cover more of the photo, which only ever makes the measured rectangle a subset of the real one. Legacy-archive rows have no local file to sample and fall back to white, bottom-left.
Migration
None, and that was verified rather than assumed.
Lexical block fields serialize into the existing
jsonbrichText columns — there are nophoto_galleryorcarouseltables and no new column. Confirmed by applyingmigrations/to a fresh database withPAYLOAD_DISABLE_PUSH=1andNODE_ENV=production, then writing and reading back a multi-credit gallery.pnpm generate:typesis likewise a no-op, since lexical blocks don't surface inpayload-types.ts.Scope
Both block definitions are mirrored in
payload.config.tsandcollections/LiveArticles.ts, so live-article updates get the same treatment — they already render through the sameSerializeLexicalpath.Checks
pnpm typecheckclean.pnpm lint0 errors (53 pre-existingmigrations/warnings, unchanged).🤖 Generated with Claude Code