Plugin Directory: server-render screenshots gallery + Gallery Lightbox Enhancements polyfill#622
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
A bit of context on how we got here: we first proposed an upstream Gutenberg fix for lightbox captions and, separately, a masonry block style for On captions specifically: every figure ships its caption text, but it surfaces only inside the lightbox overlay and never on the gallery itself. Plugin authors routinely write multi-sentence captions, and rendering those under each thumbnail would tear the brick layout apart with uneven figure heights. The lightbox is the right surface for that copy - it has room for it without compromising the grid. |
There was a problem hiding this comment.
Pull request overview
Replaces the wporg-plugins-2024 theme’s bespoke React screenshot gallery with a server-rendered [wporg-plugins-screenshots] shortcode in the Plugin Directory plugin, and introduces a small “Gallery Lightbox Enhancements” polyfill plugin to fill Gutenberg lightbox caption + gallery masonry gaps during the wp.org migration.
Changes:
- Added a new server-rendered screenshots shortcode that outputs
core/gallery+core/imageblocks, includes reveal behavior for large galleries, and adds Photon preconnect + responsivesrcsethandling. - Added a new polyfill plugin that registers an
is-style-masonryGallery style and restores lightbox caption rendering. - Removed the legacy theme JS + SCSS screenshot/gallery implementation and updated built CSS artifacts accordingly.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/package.json | Removes legacy JS build script for the old screenshot UI. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/js/build/theme.js | Deletes built legacy React screenshot bundle. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/js/build/theme.asset.php | Deletes asset metadata for the removed legacy bundle. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/functions.php | Stops enqueuing the legacy React bundle on single plugin pages. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/css/style.css | Regenerated theme CSS after removing screenshot/gallery SCSS. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/css/style-rtl.css | Regenerated RTL theme CSS after removing screenshot/gallery SCSS. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/theme.js | Removes legacy screenshot initialization entrypoint. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/styles/components/_components.scss | Stops importing the old screenshot/gallery component styles. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/screenshots/index.js | Removes old Screenshots component. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/screenshots/image-gallery.js | Removes old ImageGallery implementation. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/components/plugin/_sections-screenshots.scss | Removes legacy screenshots section styles. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/components/plugin/_sections-image-gallery.scss | Removes legacy image gallery styles. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php | New SSR shortcode: block markup generation, reveal wrapper, Photon hints, and dimension probing. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.js | Adds client-side reveal behavior + broken-image handling for large galleries. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.css | Adds shortcode-scoped masonry/grid styling and reveal UI styles. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php | Emits Photon preconnect hints on plugin singular pages. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/readme.txt | New plugin readme describing the Gutenberg polyfills. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/LICENSE | Adds GPL license file for the new plugin. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-version-guard.php | Adds a guard to self-disable the polyfill when core ships the features. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-masonry-style.php | Registers/enqueues a masonry style variation for core/gallery. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-lightbox-captions.php | Adds caption text to interactivity state and enqueues runtime assets. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/gallery-lightbox-enhancements.php | Plugin bootstrap: loads guarded features. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css | CSS for the is-style-masonry Gallery variation. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js | Runtime augmentation to render captions inside the lightbox overlay. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css | Caption + scrim styling for the lightbox overlay. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while ( $offset + 8 < $length ) { | ||
| if ( "\xFF" !== $bytes[ $offset ] ) { | ||
| return false; | ||
| } | ||
| $marker = ord( $bytes[ $offset + 1 ] ); | ||
| ++$offset; | ||
| // Skip 0xFF padding bytes between segments. | ||
| while ( 0xFF === $marker && $offset < $length ) { | ||
| $marker = ord( $bytes[ $offset ] ); | ||
| ++$offset; | ||
| } | ||
| $is_sof = ( | ||
| ( $marker >= 0xC0 && $marker <= 0xC3 ) || | ||
| ( $marker >= 0xC5 && $marker <= 0xC7 ) || | ||
| ( $marker >= 0xC9 && $marker <= 0xCB ) || | ||
| ( $marker >= 0xCD && $marker <= 0xCF ) | ||
| ); | ||
| if ( $is_sof && $offset + 7 < $length ) { | ||
| $dim = unpack( 'nheight/nwidth', substr( $bytes, $offset + 3, 4 ) ); | ||
| if ( $dim && $dim['width'] > 0 && $dim['height'] > 0 ) { | ||
| return array( $dim['width'], $dim['height'] ); | ||
| } | ||
| return false; | ||
| } | ||
| if ( $offset + 1 >= $length ) { | ||
| return false; | ||
| } | ||
| $segment_length = unpack( 'nlen', substr( $bytes, $offset, 2 ) ); | ||
| if ( ! $segment_length || $segment_length['len'] < 2 ) { | ||
| return false; | ||
| } |
| * - On init, measure the natural bottom of the ninth figure across | ||
| * the columns and write it into `--collapse-height` so the wrap | ||
| * clips at exactly the right line. | ||
| * - On click, measure the wrap's full `scrollHeight` and write it | ||
| * into `--full-height`, then flip the `is-revealed` class so the | ||
| * CSS transition animates from collapse-height to full-height. |
There was a problem hiding this comment.
Done in 814a32d - removed the unused COMPACT_VISIBLE constant, documented the $dimensions param on build_gallery_markup(), and refreshed the CSS/JS/class-header comments that still described the dropped dynamic --collapse-height measurement.
| * `--collapse-height` is set inline by the JS on init (it measures the | ||
| * natural bottom edge of the ninth figure across all three columns). | ||
| * If JS hasn't run yet, the fallback value of 36rem covers the typical | ||
| * "three rows of 16:10 landscape thumbs" footprint. |
| /** | ||
| * Builds the block markup string for the Gallery + Image blocks. | ||
| * | ||
| * Every screenshot renders into the markup — collapse / expand is | ||
| * a CSS `max-height` clip on the gallery wrap, not a DOM hide. This | ||
| * keeps the masonry balanced across columns once and prevents the | ||
| * "figures reshuffle" effect the user saw on click previously. | ||
| * | ||
| * @param array $screenshots Screenshots returned by Template::get_screenshots(). | ||
| * @param int $count Total screenshot count, used to size columns. | ||
| * @return string Block markup ready for do_blocks(). | ||
| */ | ||
| protected static function build_gallery_markup( $screenshots, $count, $dimensions = array() ) { | ||
| $inner = ''; |
2dcc0b6 to
ebb5f7a
Compare
Replaced the wporg-plugins-2024 theme JS gallery with a server-rendered [wporg-plugins-screenshots] shortcode in the Plugin Directory plugin, and added the Gallery Lightbox Enhancements polyfill that ports two pending Gutenberg pull requests (lightbox captions #77477 and the masonry block style #77615). The screenshot UX is now server-side, cacheable by Varnish, works with JS disabled, and renders through core/gallery + core/image blocks instead of bespoke theme JS. The shortcode renders every figure upfront with eager loading, intrinsic width and height attributes, and fetchpriority="high" on the cap-window thumbnails so there is zero layout shift while screenshots decode. Galleries up to nine figures show the full grid; larger ones render a "Show all N screenshots" reveal button that animates max-height on the wrap to full size with a setTimeout fallback so the cap always releases. Layout is deterministic: N=1 single tile, N=2-4 row-aligned grid, N>=5 brick masonry by default with a row-aligned grid upgrade when every screenshot shares an aspect ratio. Aspect ratios and pixel dimensions come from a single Memcached-cached probe over the screenshot URLs (md5-keyed so a revision bump auto-invalidates). Lightbox captions and a generic Masonry block style ship through the polyfill plugin so they self-disable once core lands the upstream PRs. Removed the legacy theme implementation: client/screenshots/, the dedicated theme.js entry, the build:old:js npm script, and the matching SCSS partials. The theme no longer carries any screenshot-specific JavaScript or stylesheets - the shortcode owns the entire flow. Affected: wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/, wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/, wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php, wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/
ebb5f7a to
9d889d8
Compare
There was a problem hiding this comment.
@dan-zakirov Thanks for the PR! I've added some commentary below, I've only code reviewed, not actually tried running it, but it looks like a strong start!
I'm going to fire up a PR to extract the resolution of assets at import time, and backfill them, to make the code cleaner and allow such improvements to also be used for the WordPress.org plugins API.
I haven't checked, but I think I recall the output of this is used in the WordPress.org plugins api somewhere, somehow..
| /** | ||
| * Reads the width and height from an image's header bytes, without | ||
| * touching GD or the PHP warning surface that `getimagesizefromstring` | ||
| * emits on partial / malformed input. Supports PNG, JPEG, and GIF — | ||
| * the formats Plugin Directory accepts as screenshots. | ||
| * | ||
| * @param string $bytes Raw image bytes (the first 32 KB is enough). | ||
| * @return array|false `[ $width, $height ]` on success, false otherwise. | ||
| */ | ||
| private static function parse_image_dimensions( $bytes ) { |
There was a problem hiding this comment.
I think I'd still prefer to use getimagesize() here, but instead of having it within the Screenshots handler, fetch the resolutions during the plugin import process.
If the file has malformed image headers, we can also skip importing those images... Although I'm betting this would exclude soem animated images (..what a shame...)
We can backfill the existing image metadata, such that we can have this as assuming all data is stored.
There was a problem hiding this comment.
Waiting on #628 to land - once it does I'll drop the parser here and read width/height straight from the assets_screenshots post meta.
Switched the screenshot count placeholder from %d to %s and ran $count through number_format_i18n() so locales with non-Latin digit grouping render the value correctly. Keeps the same _n() pluralisation and the same translator comment, with %s in both. Addresses dd32's review comment on lines 423-425 (PR WordPress#622).
| * @return string Image block markup. | ||
| */ | ||
| protected static function build_image_block( $screenshot, $id, $above_fold = false, $dimensions = null ) { | ||
| $src = isset( $screenshot['src'] ) ? esc_url( $screenshot['src'] ) : ''; |
| ) | ||
| ); | ||
|
|
||
| $srcset = self::photon_srcset( $src ); |
| // and `height` attributes — the browser reserves slot height | ||
| // from the intrinsic aspect ratio, so the gallery has zero | ||
| // layout shift no matter how slowly the screenshots decode. | ||
| $dimensions = self::get_dimensions( $screenshots ); |
| if ( self::is_uniform_aspect( $screenshots ) ) { | ||
| $layout_class .= ' has-uniform-aspect'; | ||
| } |
| return true; | ||
| } | ||
|
|
||
| $dimensions = self::get_dimensions( $screenshots ); |
Replaced the hand-rolled emit_preconnect() that printed <link> tags straight into wp_head with a wp_resource_hints filter callback. The filter is the native WordPress integration point for preconnect and dns-prefetch hints, lets other code dedupe and reorder, and reads more naturally next to the rest of the plugin-directory hooks. Hint now runs on every singular plugin page (production or local) — the previous production-only guard with function_exists fallback was dropped: the hint is harmless in any environment and we target modern WP without backcompat shims. Addresses dd32's review comment on lines 192-193 (PR WordPress#622).
Moved screenshots.css and screenshots.js from shortcodes/assets/ to the plugin's existing css/ and js/ directories, alongside edit-form.css and edit-form.js. The shortcode no longer creates a one-off asset folder under itself — versioning through filemtime() is now inlined directly in plugins_url() / filemtime() calls without the file_exists() fallback and the helper variables. Addresses dd32's review comments on lines 208-230 (PR WordPress#622).
| while ( $offset + 8 < $length ) { | ||
| if ( "\xFF" !== $bytes[ $offset ] ) { | ||
| return false; | ||
| } | ||
| $marker = ord( $bytes[ $offset + 1 ] ); | ||
| ++$offset; | ||
| // Skip 0xFF padding bytes between segments. | ||
| while ( 0xFF === $marker && $offset < $length ) { | ||
| $marker = ord( $bytes[ $offset ] ); | ||
| ++$offset; | ||
| } | ||
| $is_sof = ( | ||
| ( $marker >= 0xC0 && $marker <= 0xC3 ) || | ||
| ( $marker >= 0xC5 && $marker <= 0xC7 ) || | ||
| ( $marker >= 0xC9 && $marker <= 0xCB ) || | ||
| ( $marker >= 0xCD && $marker <= 0xCF ) | ||
| ); | ||
| if ( $is_sof && $offset + 7 < $length ) { | ||
| $dim = unpack( 'nheight/nwidth', substr( $bytes, $offset + 3, 4 ) ); | ||
| if ( $dim && $dim['width'] > 0 && $dim['height'] > 0 ) { | ||
| return array( $dim['width'], $dim['height'] ); | ||
| } | ||
| return false; | ||
| } | ||
| if ( $offset + 1 >= $length ) { | ||
| return false; | ||
| } | ||
| $segment_length = unpack( 'nlen', substr( $bytes, $offset, 2 ) ); | ||
| if ( ! $segment_length || $segment_length['len'] < 2 ) { | ||
| return false; | ||
| } |
| /** | ||
| * Visible figure count when collapse is active. | ||
| * | ||
| * Eight figures across three columns settles into a 3+3+2 brick — the | ||
| * partial bottom row reads as "more below" under the fade overlay. | ||
| * | ||
| * @var int | ||
| */ | ||
| const COMPACT_VISIBLE = 8; | ||
|
|
| * Reveal wrap. Clips the gallery via `max-height` while collapsed; the | ||
| * fade overlay and the Show-all button are absolutely positioned over | ||
| * the clipped area so their position adapts to the column heights — | ||
| * no dead band beneath the shortest column. | ||
| * | ||
| * `--collapse-height` is set inline by the JS on init (it measures the | ||
| * natural bottom edge of the ninth figure across all three columns). | ||
| * If JS hasn't run yet, the fallback value of 36rem covers the typical | ||
| * "three rows of 16:10 landscape thumbs" footprint. |
str_contains() requires PHP 8.0+, but the plugin header declares Requires PHP: 7.4. Switched the lightbox-container detection check to strpos() so the plugin loads cleanly on the minimum supported PHP version instead of fatal-erroring on first hook fire.
Previously the guard only watched GUTENBERG_VERSION, so on vanilla WordPress installs that never load the Gutenberg plugin the polyfill ran forever even after the upstream features landed in core. Added WP_VERSION_WITH_FEATURES alongside the Gutenberg constant: should_load() now steps aside when either core or the Gutenberg plugin ships the features, and falls back to "load" only while both constants are still sentinels. This covers the two real-world paths: wordpress.org runs trunk + the latest Gutenberg release (so the Gutenberg check fires first), and plain sites pick up the features through core (so the WP check fires).
Three documentation-only cleanups in the screenshots shortcode: - Removed the COMPACT_VISIBLE constant. It was a leftover from an earlier "render only N tiles in the collapsed state" design; the current implementation renders every figure into the DOM and clips the wrap with CSS max-height, so the constant has no callers. - Updated the class header docblock to match: it now describes the fixed max-height collapse cap instead of the removed per-tile cap. - Updated the screenshots.css and js/screenshots.js header comments the same way: they used to describe a JS measurement that wrote --collapse-height inline. The measurement was already gone, only the comments lagged behind. - Documented the new $dimensions parameter on build_gallery_markup().
|
|
||
| wp_enqueue_script( | ||
| 'wporg-plugins-screenshots', | ||
| plugins_url( 'js/screenshots.js', __DIR__ . '/../plugin-directory.php' ), | ||
| array(), | ||
| (string) filemtime( __DIR__ . '/../js/screenshots.js' ), | ||
| true | ||
| ); |
| ); | ||
| if ( $is_sof && $offset + 7 < $length ) { | ||
| $dim = unpack( 'nheight/nwidth', substr( $bytes, $offset + 3, 4 ) ); | ||
| if ( $dim && $dim['width'] > 0 && $dim['height'] > 0 ) { | ||
| return array( $dim['width'], $dim['height'] ); | ||
| } | ||
| return false; | ||
| } | ||
| if ( $offset + 1 >= $length ) { | ||
| return false; |
| * The collapse height is a fixed `max-height: 32rem` cap on the wrap | ||
| * (about three rows of typical landscape thumbs). We don't measure | ||
| * dynamically: lazy figures past the fold report `naturalWidth: 0` | ||
| * until they decode, which would make the measured height equal to | ||
| * the unclipped natural height and defeat the collapse entirely. |
Replaces the bespoke
wporg-plugins-2024theme JS screenshots gallery with a server-rendered[wporg-plugins-screenshots]shortcode in the Plugin Directory plugin, and adds a small polyfill plugin (Gallery Lightbox Enhancements) that restores two long-standing Gutenberg gaps locally so the wp.org migration can ship without waiting on core.Visual evidence
Layout cases — counts 1 to 9
Reveal flow (N≥10)
Mobile (≤599 px)
What changes
wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php— new shortcode that renders every screenshot throughcore/gallery+core/imageblocks, lightbox enabled per tile. Galleries up to 9 figures show the full grid; larger ones get aShow all N screenshotsreveal button. Layout is deterministic: N=1 single tile, N=2-4 row-aligned grid, N≥5 brick masonry by default with a row-aligned grid upgrade when every screenshot shares an aspect ratio.wp-content/plugins/gallery-lightbox-enhancements/— polyfill plugin that adds ais-style-masonryblock style forcore/galleryand rendersfigcaptiontext inside the lightbox overlay. Both behaviours are gated behindVersion_Guard::should_load(), which lets the plugin self-disable once core ships the features.wp-content/themes/pub/wporg-plugins-2024/— removes the legacy theme implementation:client/screenshots/, the dedicatedtheme.jsentry, the matching SCSS partials, and thebuild:old:jsnpm script.Why
Server-side rendering makes the gallery cacheable by Varnish, works without JS, and gives third-party scrapers and SEO crawlers the full markup. Every
<img>ships withloading="eager", intrinsicwidth/heightattributes, andfetchpriority="high"on the cap-window thumbnails, so CLS stays at 0 while screenshots decode. Aspect ratios and pixel dimensions come from a single Memcached-cached probe (md5-keyed by URL list, so a screenshot revision auto-invalidates).Closes / refs
Meta Trac (the long-standing screenshot UX gaps this PR addresses):
fetchpriority, see "Why" above)Upstream Gutenberg gaps that the polyfill plugin addresses on the user side:
Prior art on this task
Earlier attempts at the same migration that fed into this PR's design:
Safety / scope
The change is additive on the Plugin Directory side and isolated for the polyfill, with one purposeful subtraction in the theme. Map of every touched path:
wp-content/plugins/gallery-lightbox-enhancements/(new plugin, 9 files)Version_Guard::should_load()once core ships the features. Activation lives outside this PR (mu-plugin loader on production).wp-content/plugins/plugin-directory/shortcodes/class-screenshots.phpdo_shortcode( '[wporg-plugins-screenshots]' )keeps working.wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.css,screenshots.jswp-content/plugins/plugin-directory/class-plugin-directory.php(+1)add_filter( 'wp_resource_hints', [ Screenshots::class, 'add_resource_hints' ] )that contributespreconnect/dns-prefetchURLs for the Photon host on single-plugin pages. Hint only, no blocking behaviour.wp-content/themes/pub/wporg-plugins-2024/client/screenshots/(deleted)wp-content/themes/pub/wporg-plugins-2024/css/style.css,style-rtl.cssbuild:old:jsscript. No theme JS or CSS for screenshots remains — the shortcode owns the whole flow.The migration does not touch DB schema, post types, taxonomies, REST endpoints, or any sibling shortcode in
plugin-directory/shortcodes/.Notes for reviewers
phpcs.xml.distruleset (WordPress-Core / Docs / Extra, PHP 8.4 compat).Version_Guardplaceholder strategy and on the deletion of the oldclient/screenshots/modules.