Making WordPress.org

Changeset 14955


Ignore:
Timestamp:
06/10/2026 03:46:00 AM (2 months ago)
Author:
thilinah
Message:

Plugin Directory: lightbox image is rendered smaller than expected

Props thilinah, bor0, annebovelett, alh0319.
Closes https://github.com/WordPress/wordpress.org/pull/689.
Fixes #8285.

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css

    r14950 r14955  
    1313
    1414/**
    15  * The core zoom-mode `.scrim` is white at 0.9 opacity by default, but
    16  * core CSS doesn't pin it (no `top`/`left` declared). With the two
    17  * `.lightbox-image-container` siblings each `position: relative; top:
    18  * 450px`, the absolute scrim falls through to its normal-flow position
    19  * and lands somewhere around `y: 1444px` — visually below the viewport
    20  * fold. Pin it to the overlay's top-left so the dimmer actually covers
    21  * the page as intended.
     15 * Defensive pin for the core zoom-mode `.scrim` (white, 0.9 opacity).
     16 * Core leaves it `position: absolute` with no `top`/`left`, relying on
     17 * its two `.lightbox-image-container` siblings being out of normal flow
     18 * so the scrim's static position resolves to the overlay's top-left.
     19 * We keep those containers on core's own positioning (see note below),
     20 * so this is belt-and-braces — harmless if core already covers the page.
    2221 */
    2322.wp-lightbox-overlay .scrim {
     
    2625}
    2726
    28 .wp-lightbox-overlay .lightbox-image-container {
    29         position: relative;
    30 }
     27/*
     28 * Do NOT set `.lightbox-image-container` to `position: relative`. Core
     29 * stacks its two containers (zoom-thumbnail + full-res) as absolutely
     30 * positioned, overlapping siblings; forcing them into normal flow stacks
     31 * the images vertically and renders the picture twice once both hold a
     32 * real image (e.g. plugin screenshots). The absolute container is already
     33 * the caption's containing block — no override needed.
     34 */
    3135
    3236.wp-lightbox-overlay .lightbox-image-container > figcaption.wp-lightbox-caption {
     
    3943        padding: 3.5em 1.25em 1em;
    4044        color: #fff;
    41         text-align: center;
     45        text-align: start;
    4246        font-size: 0.95rem;
    4347        font-weight: 500;
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js

    r14950 r14955  
    55 * contract of https://github.com/WordPress/gutenberg/pull/77477.
    66 *
    7  * Why "visible": core renders two `.lightbox-image-container` siblings.
    8  * One holds the small thumbnail used for the zoom animation hand-off,
    9  * the other holds the full-resolution image. Which one is on screen
    10  * depends on the lifecycle stage (opening, navigating, closing). We
    11  * pick the container whose <img> is actually inside the viewport at
    12  * sync time, mount the caption there, and let CSS pin it to its
    13  * parent's bottom — which is the bottom of the displayed picture.
     7 * Why "visible": core renders two center-overlapping
     8 * `.lightbox-image-container` siblings. One holds the small thumbnail
     9 * used for the zoom animation hand-off, the other the full-resolution
     10 * image, which paints on top. We mount the caption into that top
     11 * container — the last one on screen — so it pins to the bottom of the
     12 * displayed picture and is not occluded by the enlarged image, then let
     13 * CSS anchor it to the absolutely-positioned container frame.
    1414 *
    1515 * `data-wp-text` / `data-wp-bind` attributes added after the
     
    4949        function getVisibleContainer( overlay ) {
    5050                var containers = overlay.querySelectorAll( '.lightbox-image-container' );
    51                 for ( var i = 0; i < containers.length; i++ ) {
     51                // Iterate from the end: core's containers center-overlap and the
     52                // last one (the full-resolution image) paints on top, so the
     53                // caption must live there or it renders behind the enlarged image.
     54                for ( var i = containers.length - 1; i >= 0; i-- ) {
    5255                        var img = containers[ i ].querySelector( 'img' );
    5356                        if ( img && isInViewport( img ) ) {
     
    5558                        }
    5659                }
    57                 // Fallback: first container — wrong-spot caption beats no caption at all.
    58                 return containers[ 0 ] || null;
     60                // Fallback: last container — wrong-spot caption beats no caption at all.
     61                return containers[ containers.length - 1 ] || null;
    5962        }
    6063
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php

    r14950 r14955  
    6161
    6262        /**
     63         * Per-request lightbox metadata, keyed by synthetic attachment id.
     64         *
     65         * Each entry holds the full-resolution `ps.w.org` URL and intrinsic
     66         * width / height for one screenshot. {@see self::fix_lightbox_metadata()}
     67         * reads this map to repair the core Image block's lightbox state — see
     68         * that method for why the repair is necessary.
     69         *
     70         * @var array<int, array{url:string,width:(int|string),height:(int|string)}>
     71         */
     72        protected static $lightbox_meta = array();
     73
     74        /**
    6375         * Renders the shortcode output.
    6476         *
     
    101113                // scopes the change to galleries that carry our marker class,
    102114                // so other Gallery blocks on the page render unchanged.
     115                //
     116                // Pair the layout filter with the lightbox-metadata repair. Our
     117                // screenshots are external `ps.w.org` assets with synthetic
     118                // attachment ids, so core cannot resolve the full-resolution
     119                // source or dimensions the lightbox needs; fix_lightbox_metadata()
     120                // supplies them once core has assigned each image its render-time
     121                // state key. It hooks at priority 20 so it runs after core's own
     122                // lightbox filter (priority 15) and overrides the broken values.
    103123                add_filter( 'render_block_core/gallery', array( __CLASS__, 'strip_layout_classes' ), 20, 1 );
     124                add_filter( 'render_block_core/image', array( __CLASS__, 'fix_lightbox_metadata' ), 20, 2 );
    104125                $markup   = self::build_gallery_markup( $screenshots, $count, $dimensions );
    105126                $rendered = do_blocks( $markup );
     127                remove_filter( 'render_block_core/image', array( __CLASS__, 'fix_lightbox_metadata' ), 20 );
    106128                remove_filter( 'render_block_core/gallery', array( __CLASS__, 'strip_layout_classes' ), 20 );
    107129
     
    228250                $inner = '';
    229251                $index = 0;
     252
     253                // Reset the lightbox metadata map for this render — build_image_block()
     254                // fills it as it mints each synthetic id.
     255                self::$lightbox_meta = array();
    230256
    231257                foreach ( $screenshots as $screenshot_num => $screenshot ) {
     
    341367                $class  = 'wp-block-image size-large';
    342368
     369                // Record the full-resolution source and intrinsic dimensions for the
     370                // lightbox-state repair in fix_lightbox_metadata(). The grid thumbnail
     371                // loads a Photon-shrunk srcset candidate, so core (which has no real
     372                // attachment to query) would otherwise enlarge that small image; this
     373                // hands the lightbox the lossless original at its true size.
     374                self::$lightbox_meta[ (int) $id ] = array(
     375                        'url'    => $src,
     376                        'width'  => ( is_array( $dimensions ) && ! empty( $dimensions[0] ) ) ? (int) $dimensions[0] : 'none',
     377                        'height' => ( is_array( $dimensions ) && ! empty( $dimensions[1] ) ) ? (int) $dimensions[1] : 'none',
     378                );
     379
    343380                // `width` and `height` ship the screenshot's intrinsic
    344381                // dimensions so the browser reserves layout space from the
     
    398435
    399436        /**
     437         * Repairs the core Image block lightbox state for screenshot images.
     438         *
     439         * Plugin screenshots are external `ps.w.org` assets with no real
     440         * attachment, so each block carries a *synthetic* id (see
     441         * {@see self::SYNTHETIC_ID_OFFSET}). Core's lightbox renderer
     442         * (`block_core_image_render_lightbox()`, priority 15 on this same
     443         * filter) treats that id as a genuine attachment:
     444         *
     445         *   $uploadedSrc = wp_get_attachment_url( $id );        // → false
     446         *   $meta        = wp_get_attachment_metadata( $id );   // → false
     447         *   $targetWidth = $meta['width']  ?? 'none';           // → 'none'
     448         *   $targetHeight= $meta['height'] ?? 'none';           // → 'none'
     449         *
     450         * The empty `uploadedSrc` leaves the lightbox with no full-resolution
     451         * image to enlarge, and the `'none'` dimensions make core's view
     452         * script fall back to the *thumbnail's* natural size — which on
     453         * production is a Photon-shrunk srcset candidate (≤900px, often the
     454         * 300px tile). The enlarged view therefore renders tiny. On
     455         * environments without Photon the thumbnail is the full-resolution
     456         * original, which is why the bug is invisible on local / staging.
     457         *
     458         * Core keys its lightbox metadata by a per-render `uniqid()` (exposed
     459         * on the figure's `data-wp-context`), not by the attachment id, so the
     460         * only way to correct it is to read that generated key back out of the
     461         * rendered markup and re-set the affected fields. `wp_interactivity_state()`
     462         * merges with `array_replace_recursive()` (later call wins), and this
     463         * filter runs at priority 20 — after core's priority-15 pass — so the
     464         * corrected values override the broken ones. `lightboxSrcset` is
     465         * cleared so the enlarged image loads the lossless original rather than
     466         * a capped Photon candidate.
     467         *
     468         * @param string $block_content Rendered Image block markup.
     469         * @param array  $parsed_block  Parsed block, including `attrs['id']`.
     470         * @return string The unmodified markup (only interactivity state is changed).
     471         */
     472        public static function fix_lightbox_metadata( $block_content, $parsed_block ) {
     473                $id = isset( $parsed_block['attrs']['id'] ) ? (int) $parsed_block['attrs']['id'] : 0;
     474                if ( $id < self::SYNTHETIC_ID_OFFSET || ! isset( self::$lightbox_meta[ $id ] ) ) {
     475                        return $block_content;
     476                }
     477
     478                if ( ! function_exists( 'wp_interactivity_state' ) ) {
     479                        return $block_content;
     480                }
     481
     482                // Recover the render-time state key core assigned to this image.
     483                $processor = new \WP_HTML_Tag_Processor( $block_content );
     484                if ( ! $processor->next_tag( 'figure' ) ) {
     485                        return $block_content;
     486                }
     487                $context = $processor->get_attribute( 'data-wp-context' );
     488                if ( ! is_string( $context ) ) {
     489                        return $block_content;
     490                }
     491                $decoded  = json_decode( $context, true );
     492                $image_id = ( is_array( $decoded ) && isset( $decoded['imageId'] ) ) ? $decoded['imageId'] : '';
     493                if ( '' === $image_id ) {
     494                        return $block_content;
     495                }
     496
     497                $meta = self::$lightbox_meta[ $id ];
     498                wp_interactivity_state(
     499                        'core/image',
     500                        array(
     501                                'metadata' => array(
     502                                        $image_id => array(
     503                                                'uploadedSrc'    => $meta['url'],
     504                                                'targetWidth'    => $meta['width'],
     505                                                'targetHeight'   => $meta['height'],
     506                                                'lightboxSrcset' => false,
     507                                        ),
     508                                ),
     509                        )
     510                );
     511
     512                return $block_content;
     513        }
     514
     515        /**
    400516         * Wraps the rendered gallery in a reveal container with a single
    401517         * "Show all N screenshots" button below it. Click reveals every
     
    450566         * width-bound copy at ~10× smaller payload — see
    451567         * https://developer.wordpress.com/docs/photon/ for the resize/optim
    452          * options. The lightbox `src` (the unprefixed `ps.w.org` URL) stays the
    453          * full-resolution original so users get the lossless image when they
    454          * enlarge a screenshot.
     568         * options. The grid thumbnail therefore loads a small Photon candidate;
     569         * the lightbox is pointed back at the full-resolution `ps.w.org` original
     570         * separately by {@see self::fix_lightbox_metadata()} so users still get
     571         * the lossless image when they enlarge a screenshot.
    455572         *
    456573         * @param string $src Original asset URL.
     
    463580                }
    464581
    465                 // Photon (i0.wp.com) is a production-only optimisation. In local
    466                 // or staging environments the proxy may not be reachable, which
    467                 // would leave the gallery silently empty until the cold cache
    468                 // warmed up. Fall back to the unoptimised `ps.w.org` URL there.
     582                // Photon (i0.wp.com) only runs on production and staging. In local
     583                // or other environments the proxy may not be reachable, which would
     584                // leave the gallery silently empty until the cold cache warmed up.
     585                // Fall back to the unoptimised `ps.w.org` URL there.
    469586                $env = function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production';
    470                 if ( 'production' !== $env ) {
     587                if ( 'production' !== $env && 'staging' !== $env ) {
    471588                        return '';
    472589                }
Note: See TracChangeset for help on using the changeset viewer.