From 5ce6ff91bf94722f2453ac41f7d6ea29332e65a5 Mon Sep 17 00:00:00 2001 From: Dan Zakirov Date: Fri, 12 Jun 2026 16:03:13 +0300 Subject: [PATCH 1/3] FIX: prevent stale plugin screenshot thumbnails Remove the Photon srcset for Plugin Directory screenshots so replaced assets keep using the ps.w.org URL with its revision query. Keep the Gallery block, lightbox, layout detection, captions, and reveal behavior unchanged. --- .../class-plugin-directory.php | 1 - .../plugin-directory/js/screenshots.js | 6 +- .../shortcodes/class-screenshots.php | 102 ++---------------- 3 files changed, 13 insertions(+), 96 deletions(-) diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php index 105c678dfc..51e7a9dcbf 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php @@ -469,7 +469,6 @@ public function register_shortcodes() { add_shortcode( Shortcodes\Release_Confirmation::SHORTCODE, array( __NAMESPACE__ . '\Shortcodes\Release_Confirmation', 'display' ) ); add_action( 'template_redirect', array( __NAMESPACE__ . '\Shortcodes\Release_Confirmation', 'template_redirect' ) ); - add_filter( 'wp_resource_hints', array( __NAMESPACE__ . '\Shortcodes\Screenshots', 'add_resource_hints' ), 10, 2 ); } /** diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/js/screenshots.js b/wordpress.org/public_html/wp-content/plugins/plugin-directory/js/screenshots.js index 231d6bc6df..ae79dcee3b 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/js/screenshots.js +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/js/screenshots.js @@ -77,9 +77,9 @@ /* * Some screenshots end up "loaded" by the browser but with zero - * natural dimensions — typically Photon returning an empty 200, - * or a partial / pending response that the network stack never - * settles. The load / error events don't always fire for those, + * natural dimensions, usually after a zero-byte or partial response + * that the network stack never settles. The load / error events + * don't always fire for those, * so re-sweep after a short delay and hide anything that's still * degenerate. The `markBroken` guard against `!complete` keeps * lazy figures past the fold safe from this sweep. diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php index 75fbf13d73..fc4038465b 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php @@ -180,34 +180,6 @@ public static function strip_layout_classes( $content ) { return $processor->get_updated_html(); } - /** - * Adds preconnect / dns-prefetch hints to the Photon CDN host on - * single-plugin pages so the browser can warm up the TLS handshake - * while the page HTML is still streaming. Saves ~50–150 ms on the - * first thumbnail paint for cold visitors. Hooked from - * `class-plugin-directory.php` via the `wp_resource_hints` filter. - * - * @param array $urls Resource hint URLs already queued for $relation_type. - * @param string $relation_type One of preconnect / dns-prefetch / prerender / prefetch. - * @return array - */ - public static function add_resource_hints( $urls, $relation_type ) { - if ( ! is_singular( 'plugin' ) ) { - return $urls; - } - - if ( 'preconnect' === $relation_type ) { - $urls[] = array( - 'href' => 'https://i0.wp.com', - 'crossorigin' => 'anonymous', - ); - } elseif ( 'dns-prefetch' === $relation_type ) { - $urls[] = 'https://i0.wp.com'; - } - - return $urls; - } - /** * Enqueues the shortcode's own CSS and toggle script. Assets live * under the plugin's `css/` and `js/` directories alongside the @@ -363,14 +335,12 @@ protected static function build_image_block( $screenshot, $id, $above_fold = fal ) ); - $srcset = self::photon_srcset( $src ); - $class = 'wp-block-image size-large'; + $class = 'wp-block-image size-large'; // Record the full-resolution source and intrinsic dimensions for the - // lightbox-state repair in fix_lightbox_metadata(). The grid thumbnail - // loads a Photon-shrunk srcset candidate, so core (which has no real - // attachment to query) would otherwise enlarge that small image; this - // hands the lightbox the lossless original at its true size. + // lightbox-state repair in fix_lightbox_metadata(). Screenshot assets + // have no real attachment for core to query, so this hands the + // lightbox the original `ps.w.org` image at its true size. self::$lightbox_meta[ (int) $id ] = array( 'url' => $src, 'width' => ( is_array( $dimensions ) && ! empty( $dimensions[0] ) ) ? (int) $dimensions[0] : 'none', @@ -411,11 +381,10 @@ protected static function build_image_block( $screenshot, $id, $above_fold = fal $src ); $figure .= sprintf( - '%2$s', + '%2$s', $src, esc_attr( $alt ), $id, - $srcset, $dim_attrs, esc_attr( $priority ) ); @@ -449,11 +418,9 @@ protected static function build_image_block( $screenshot, $id, $above_fold = fal * * The empty `uploadedSrc` leaves the lightbox with no full-resolution * image to enlarge, and the `'none'` dimensions make core's view - * script fall back to the *thumbnail's* natural size — which on - * production is a Photon-shrunk srcset candidate (≤900px, often the - * 300px tile). The enlarged view therefore renders tiny. On - * environments without Photon the thumbnail is the full-resolution - * original, which is why the bug is invisible on local / staging. + * script fall back to the thumbnail's natural size. The rendered + * thumbnail may be constrained by layout, but the enlarged view should + * always use the original screenshot and its recorded dimensions. * * Core keys its lightbox metadata by a per-render `uniqid()` (exposed * on the figure's `data-wp-context`), not by the attachment id, so the @@ -461,9 +428,8 @@ protected static function build_image_block( $screenshot, $id, $above_fold = fal * rendered markup and re-set the affected fields. `wp_interactivity_state()` * merges with `array_replace_recursive()` (later call wins), and this * filter runs at priority 20 — after core's priority-15 pass — so the - * corrected values override the broken ones. `lightboxSrcset` is - * cleared so the enlarged image loads the lossless original rather than - * a capped Photon candidate. + * corrected values override the broken ones. `lightboxSrcset` is cleared + * so the enlarged image loads the original screenshot URL. * * @param string $block_content Rendered Image block markup. * @param array $parsed_block Parsed block, including `attrs['id']`. @@ -554,54 +520,6 @@ protected static function wrap_with_show_all_button( $rendered_gallery, $count ) . ''; } - /** - * Builds a Photon-powered `srcset` (and matching `sizes`) attribute string - * for a `ps.w.org` screenshot URL. Returns an empty string when the source - * URL is not on `ps.w.org`, so the original `src` is used unchanged. - * - * Plugin authors upload screenshots at full resolution but we render them - * inside a 3-column grid, so the browser otherwise downloads the full - * asset (often 300–800 KB) only to scale it down to a ~250 px tile. - * Routing the URL through `i0.wp.com` (Photon) returns a re-encoded, - * width-bound copy at ~10× smaller payload — see - * https://developer.wordpress.com/docs/photon/ for the resize/optim - * options. The grid thumbnail therefore loads a small Photon candidate; - * the lightbox is pointed back at the full-resolution `ps.w.org` original - * separately by {@see self::fix_lightbox_metadata()} so users still get - * the lossless image when they enlarge a screenshot. - * - * @param string $src Original asset URL. - * @return string Attribute fragment ready to interpolate into ``, - * including the leading space, or empty string. - */ - protected static function photon_srcset( $src ) { - if ( ! preg_match( '#^https?://ps\.w\.org/#', $src ) ) { - return ''; - } - - // Photon (i0.wp.com) only runs on production and staging. In local - // or other environments the proxy may not be reachable, which would - // leave the gallery silently empty until the cold cache warmed up. - // Fall back to the unoptimised `ps.w.org` URL there. - $env = function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production'; - if ( 'production' !== $env && 'staging' !== $env ) { - return ''; - } - - $photon_base = preg_replace( '#^https?://#', 'https://i0.wp.com/', $src ); - $widths = array( 300, 600, 900 ); - $srcset = array(); - - foreach ( $widths as $width ) { - $srcset[] = add_query_arg( 'w', $width, $photon_base ) . ' ' . $width . 'w'; - } - - return sprintf( - ' srcset="%1$s" sizes="(max-width: 599px) 50vw, 33vw"', - esc_attr( implode( ', ', $srcset ) ) - ); - } - /** * Whether the gallery should swap from brick masonry to row-aligned grid. * From d592c46eb26079f41536c89a2472614bbb5cfa9a Mon Sep 17 00:00:00 2001 From: Dan Zakirov Date: Sun, 14 Jun 2026 18:37:40 +0300 Subject: [PATCH 2/3] FIX: stabilize Plugin Directory lightbox captions Render screenshot captions in an overlay-level bar beneath the active image so long captions no longer cover screenshot content. Reserve viewport space before revealing captions and keep the core lightbox scale unitless so zoom in and zoom out remain stable for vertical screenshots. Reduce thumbnail hover repaint work by removing the frosted trigger backdrop in screenshot galleries. --- .../assets/lightbox-captions.css | 89 +- .../assets/lightbox-captions.js | 760 +++++++++++++++++- .../assets/masonry.css | 5 + .../plugin-directory/css/screenshots.css | 6 + 4 files changed, 794 insertions(+), 66 deletions(-) diff --git a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css index 2ddd30920c..2fa776962e 100644 --- a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css +++ b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css @@ -1,14 +1,10 @@ /** * Caption styling for the core image lightbox overlay. * - * Polyfill for https://github.com/WordPress/gutenberg/pull/77477. - * - * Caption is mounted inside the visible `.lightbox-image-container` - * (the JS picks the one whose is on screen) so it pins to the - * bottom edge of the picture frame, not the bottom of the viewport. - * The visual treatment matches the PR #77477 reference exactly: - * white text with a soft drop shadow on a barely-there bottom-up - * scrim, anchored flush to the image. + * The gallery keeps the core lightbox animation and navigation but + * renders the caption as a dedicated bar beneath the active image. + * This avoids competing with text-heavy screenshots while still keeping + * short captions compact and visually centered. */ /** @@ -25,52 +21,73 @@ } /* - * Do NOT set `.lightbox-image-container` to `position: relative`. Core - * stacks its two containers (zoom-thumbnail + full-res) as absolutely - * positioned, overlapping siblings; forcing them into normal flow stacks - * the images vertically and renders the picture twice once both hold a - * real image (e.g. plugin screenshots). The absolute container is already - * the caption's containing block — no override needed. + * The caption stays at the overlay level. Core already exposes the + * active image dimensions through CSS custom properties on the overlay, + * so the bar can align itself below the current image without changing + * the lightbox container flow. */ -.wp-lightbox-overlay .lightbox-image-container > figcaption.wp-lightbox-caption { +.wp-lightbox-overlay > figcaption.wp-lightbox-caption { position: absolute; - bottom: 0; - left: 0; - right: 0; + top: var(--wporg-lightbox-caption-top, calc(50% + ( var(--wp--lightbox-container-height) / 2 ) + 0.5rem)); + left: 50%; + transform: translateX(-50%); z-index: 3000001; + box-sizing: border-box; + width: min(var(--wporg-lightbox-caption-width, var(--wp--lightbox-container-width)), calc(100% - 1rem)); + max-width: calc(100% - 1rem); margin: 0; - padding: 3.5em 1.25em 1em; - color: #fff; - text-align: start; - font-size: 0.95rem; - font-weight: 500; - line-height: 1.45; - letter-spacing: 0.005em; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55); - - /* Almost invisible scrim — present only to guarantee contrast on - bright photos. The text-shadow does most of the legibility work. */ - background: linear-gradient(to top, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0) 100%); + padding: 0.75rem 1rem; + color: #1e1e1e; + text-align: center; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0; + text-shadow: none; + background: rgba(255, 255, 255, 0.98); + border: 0; + border-radius: 12px; + overflow-wrap: anywhere; + text-wrap: pretty; pointer-events: none; opacity: 0; - transition: opacity 160ms ease-in; + transition: opacity 120ms ease; } -.wp-lightbox-overlay.active .lightbox-image-container > figcaption.wp-lightbox-caption.has-caption { +.wp-lightbox-overlay.active > figcaption.wp-lightbox-caption.has-caption.is-ready { opacity: 1; - transition: opacity 220ms ease-out 200ms; } /* Hide the caption when there is no text to show. */ -.wp-lightbox-overlay .lightbox-image-container > figcaption.wp-lightbox-caption:not(.has-caption) { +.wp-lightbox-overlay > figcaption.wp-lightbox-caption:not(.has-caption) { visibility: hidden; } +@media (min-width: 960px) { + + .wp-lightbox-overlay > figcaption.wp-lightbox-caption { + top: var(--wporg-lightbox-caption-top, calc(50% + ( var(--wp--lightbox-container-height) / 2 ) + 0.75rem)); + padding: 0.875rem 1.25rem; + font-size: 0.95rem; + } +} + +@media (max-width: 480px), (max-height: 700px) { + + .wp-lightbox-overlay > figcaption.wp-lightbox-caption { + top: var(--wporg-lightbox-caption-top, calc(50% + ( var(--wp--lightbox-container-height) / 2 ) + 0.375rem)); + padding: 0.625rem 0.875rem; + font-size: 0.8125rem; + border-radius: 10px; + } + +} + @media (prefers-reduced-motion: reduce) { - .wp-lightbox-overlay .lightbox-image-container > figcaption.wp-lightbox-caption, - .wp-lightbox-overlay.active .lightbox-image-container > figcaption.wp-lightbox-caption.has-caption { + .wp-lightbox-overlay > figcaption.wp-lightbox-caption, + .wp-lightbox-overlay.active > figcaption.wp-lightbox-caption.has-caption { transition: none; } } diff --git a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js index 158d58cfe7..2878581f10 100644 --- a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js +++ b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js @@ -1,16 +1,12 @@ /** - * Augments the core lightbox overlay with a figcaption mounted inside - * the *visible* enlarged-image container, so the caption sits flush - * against the bottom edge of the picture frame — exactly the visual - * contract of https://github.com/WordPress/gutenberg/pull/77477. + * Augments the core lightbox overlay with a figcaption mounted at the + * overlay level, beneath the active image. * - * Why "visible": core renders two center-overlapping - * `.lightbox-image-container` siblings. One holds the small thumbnail - * used for the zoom animation hand-off, the other the full-resolution - * image, which paints on top. We mount the caption into that top - * container — the last one on screen — so it pins to the bottom of the - * displayed picture and is not occluded by the enlarged image, then let - * CSS anchor it to the absolutely-positioned container frame. + * Core still owns the zoom animation, navigation buttons, and the + * active image container geometry. This layer only reads the active + * image's caption text, renders a dedicated caption bar below the + * image, and switches the text alignment when the rendered caption + * grows beyond a short-label treatment. * * `data-wp-text` / `data-wp-bind` attributes added after the * Interactivity runtime parsed the page do not bind, so the caption is @@ -18,6 +14,9 @@ */ ( function () { + var CAPTION_REVEAL_DELAY = 460; + var SCREENSHOT_ID_OFFSET = 9000000; + /** * Whether the element overlaps the current viewport, even partially. * @@ -62,24 +61,644 @@ } /** - * Moves the caption node into the target container if it isn't - * already attached there. + * Keeps the caption as a direct child of the overlay so it can sit in + * a dedicated bar beneath the active image without disturbing core's + * overlapping lightbox-image containers. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element to move. + */ + function moveCaptionIntoOverlay( overlay, caption ) { + var insertBefore = overlay.querySelector( '.screen-reader-text, .scrim' ); + if ( caption.parentNode !== overlay ) { + overlay.insertBefore( caption, insertBefore || null ); + } + } + + /** + * Extracts the attachment-style id from a lightbox image class. + * + * Screenshot gallery images use a high synthetic id range so they can be + * distinguished from ordinary site images that should keep core behavior. + * + * @param {HTMLImageElement|null} img Lightbox image element. + * @return {number} + */ + function getImageBlockId( img ) { + var match; + + if ( ! img || ! img.className ) { + return 0; + } + + match = img.className.match( /\bwp-image-(\d+)\b/ ); + + if ( ! match ) { + return 0; + } + + return parseInt( match[1], 10 ) || 0; + } + + /** + * Whether the current lightbox image belongs to the Plugin Directory + * screenshots gallery rather than some other site lightbox. + * + * @param {HTMLImageElement|null} img Lightbox image element. + * @return {boolean} + */ + function isManagedImage( img ) { + return getImageBlockId( img ) >= SCREENSHOT_ID_OFFSET; + } + + /** + * Whether the active overlay currently shows a managed screenshot image. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @return {boolean} + */ + function isManagedOverlay( overlay ) { + var target = getVisibleContainer( overlay ); + var img = target ? target.querySelector( 'img' ) : null; + + return isManagedImage( img ); + } + + /** + * Whether the lightbox overlay is currently active. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @return {boolean} + */ + function isOverlayActive( overlay ) { + return overlay.classList.contains( 'active' ); + } + + /** + * Whether the overlay has finished its current transition. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @return {boolean} + */ + function isOverlaySettled( overlay ) { + return overlay.dataset.wporgCaptionSettled === '1'; + } + + /** + * Marks the overlay as still transitioning. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function markOverlayUnsettled( overlay ) { + overlay.dataset.wporgCaptionSettled = '0'; + } + + /** + * Marks the overlay as stable enough for geometry-sensitive caption work. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function markOverlaySettled( overlay ) { + overlay.dataset.wporgCaptionSettled = '1'; + } + + /** + * Clears cached core lightbox dimensions so the next settled sync starts + * from the current image rather than a stale transition frame. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function clearOverlayMetricCache( overlay ) { + delete overlay.dataset.wporgLightboxBaseKey; + delete overlay.dataset.wporgLightboxContainerWidth; + delete overlay.dataset.wporgLightboxContainerHeight; + delete overlay.dataset.wporgLightboxImageWidth; + delete overlay.dataset.wporgLightboxImageHeight; + delete overlay.dataset.wporgLightboxScale; + delete overlay.dataset.wporgLightboxGeometryApplied; + } + + /** + * Clears any pending deferred caption sync. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function clearDeferredSync( overlay ) { + if ( overlay.wporgCaptionSyncTimer ) { + window.clearTimeout( overlay.wporgCaptionSyncTimer ); + overlay.wporgCaptionSyncTimer = null; + } + } + + /** + * Clears any pending caption reveal. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function clearCaptionReveal( overlay ) { + if ( overlay.wporgCaptionRevealTimer ) { + window.clearTimeout( overlay.wporgCaptionRevealTimer ); + overlay.wporgCaptionRevealTimer = null; + } + } + + /** + * Reveals the caption after layout-sensitive sizing has settled. + * + * Delaying the opacity flip avoids showing the caption while the + * image and caption widths are still being recomputed. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element. + */ + function scheduleCaptionReveal( overlay, caption ) { + clearCaptionReveal( overlay ); + + if ( ! isOverlayActive( overlay ) || ! caption.classList.contains( 'has-caption' ) ) { + return; + } + + overlay.wporgCaptionRevealTimer = window.setTimeout( function () { + overlay.wporgCaptionRevealTimer = null; + + if ( ! isOverlayActive( overlay ) || ! caption.classList.contains( 'has-caption' ) ) { + return; + } + + caption.classList.add( 'is-ready' ); + }, 24 ); + } + + /** + * Hides the caption without touching the current lightbox geometry. + * + * We intentionally leave core sizing variables as-is during the close + * animation so the zoom-back transition does not snap. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element. + */ + function hideCaption( overlay, caption ) { + clearCaptionReveal( overlay ); + caption.classList.remove( 'has-caption', 'is-long-caption', 'is-ready' ); + caption.style.removeProperty( '--wporg-lightbox-caption-width' ); + caption.style.removeProperty( '--wporg-lightbox-caption-top' ); + caption.style.removeProperty( '--wporg-lightbox-caption-max-height' ); + } + + /** + * Returns the number of rendered text lines inside the caption. + * + * The CSS keeps a stable width for the caption bar, so the rendered + * line count reflects the real lightbox layout on both desktop and + * narrow mobile viewports. + * + * @param {HTMLElement} caption The figcaption element. + * @return {number} + */ + function getRenderedLineCount( caption ) { + var styles = window.getComputedStyle( caption ); + var lineHeight = parseFloat( styles.lineHeight ); + var paddingTop = parseFloat( styles.paddingTop ) || 0; + var paddingBottom = parseFloat( styles.paddingBottom ) || 0; + + if ( ! lineHeight ) { + return 0; + } + + return Math.max( + 0, + ( caption.getBoundingClientRect().height - paddingTop - paddingBottom ) / lineHeight + ); + } + + /** + * Whether two rectangles overlap. * - * @param {HTMLElement|null} target Container that should host the caption. - * @param {HTMLElement} caption The figcaption element to move. + * @param {DOMRect} a First rectangle. + * @param {DOMRect|null} b Second rectangle. + * @return {boolean} + */ + function rectsOverlap( a, b ) { + return !! b && ! ( + a.right <= b.left || + a.left >= b.right || + a.bottom <= b.top || + a.top >= b.bottom + ); + } + + /** + * Reads a numeric lightbox size variable off the overlay. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {string} variableName CSS custom property name. + * @return {number} */ - function moveCaptionInto( target, caption ) { - if ( target && caption.parentNode !== target ) { - target.appendChild( caption ); + function getOverlayMetric( overlay, variableName ) { + var inlineValue = overlay.style.getPropertyValue( variableName ); + var computedValue = inlineValue || window.getComputedStyle( overlay ).getPropertyValue( variableName ); + var parsedValue = parseFloat( computedValue ); + + return Number.isFinite( parsedValue ) ? parsedValue : 0; + } + + /** + * Stores the current lightbox image dimensions for the active image and viewport. + * + * Core recalculates these values when the user navigates between images or + * resizes the viewport. We cache the current set so the caption logic can + * temporarily shrink the visible image and still restore the original values + * before the next measurement pass. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLImageElement|null} img The active lightbox image. + */ + function cacheOverlayMetrics( overlay, img ) { + var key = ( img ? img.getAttribute( 'src' ) || '' : '' ) + + '@' + window.innerWidth + 'x' + window.innerHeight; + + if ( overlay.dataset.wporgLightboxBaseKey === key ) { + return; } + + overlay.dataset.wporgLightboxBaseKey = key; + overlay.dataset.wporgLightboxContainerWidth = String( + getOverlayMetric( overlay, '--wp--lightbox-container-width' ) + ); + overlay.dataset.wporgLightboxContainerHeight = String( + getOverlayMetric( overlay, '--wp--lightbox-container-height' ) + ); + overlay.dataset.wporgLightboxImageWidth = String( + getOverlayMetric( overlay, '--wp--lightbox-image-width' ) + ); + overlay.dataset.wporgLightboxImageHeight = String( + getOverlayMetric( overlay, '--wp--lightbox-image-height' ) + ); + overlay.dataset.wporgLightboxScale = String( + getOverlayMetric( overlay, '--wp--lightbox-scale' ) + ); + } + + /** + * Restores the lightbox image dimensions captured from core. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function resetOverlayMetrics( overlay ) { + var metrics = { + '--wp--lightbox-container-width': overlay.dataset.wporgLightboxContainerWidth, + '--wp--lightbox-container-height': overlay.dataset.wporgLightboxContainerHeight, + '--wp--lightbox-image-width': overlay.dataset.wporgLightboxImageWidth, + '--wp--lightbox-image-height': overlay.dataset.wporgLightboxImageHeight, + '--wp--lightbox-scale': overlay.dataset.wporgLightboxScale, + }; + + Object.keys( metrics ).forEach( function ( variableName ) { + if ( ! metrics[ variableName ] ) { + return; + } + + if ( variableName === '--wp--lightbox-scale' ) { + overlay.style.setProperty( variableName, metrics[ variableName ] ); + return; + } + + overlay.style.setProperty( variableName, metrics[ variableName ] + 'px' ); + } ); + } + + /** + * Reads the rendered image dimensions from the active lightbox image. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLImageElement|null} img The active lightbox image. + * @return {{width:number,height:number}} + */ + function getRenderedImageMetrics( overlay, img ) { + var overlayWidth = getOverlayMetric( overlay, '--wp--lightbox-image-width' ); + var overlayHeight = getOverlayMetric( overlay, '--wp--lightbox-image-height' ); + var imageRect = img ? img.getBoundingClientRect() : null; + + if ( overlayWidth && overlayHeight ) { + return { + width: overlayWidth, + height: overlayHeight, + }; + } + + return { + width: imageRect ? imageRect.width : getOverlayMetric( overlay, '--wp--lightbox-image-width' ), + height: imageRect ? imageRect.height : getOverlayMetric( overlay, '--wp--lightbox-image-height' ), + }; + } + + /** + * Whether the active screenshot is portrait-shaped or otherwise very narrow. + * + * Those screenshots already get a wider caption bar and need the fitting + * math to use the intended lightbox geometry, not the current animation + * frame, otherwise the open transition can visibly jump mid-flight. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLImageElement|null} img The active lightbox image. + * @return {boolean} + */ + function isPortraitOrNarrowImage( overlay, img ) { + var imageMetrics = getRenderedImageMetrics( overlay, img ); + var aspectRatio = imageMetrics.width ? imageMetrics.height / imageMetrics.width : 0; + + return aspectRatio > 1.2 || ( imageMetrics.width > 0 && imageMetrics.width < 320 ); + } + + /** + * Shrinks the lightbox image proportionally when a below-image caption would + * otherwise fall out of the viewport or into the navigation controls. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element. + * @param {HTMLImageElement|null} img The active lightbox image. + */ + function fitCaptionWithinViewport( overlay, caption, img ) { + var bottomPadding = 16; + var attempt = 0; + var minContainerScale = 0.5; + var nextButton = overlay.querySelector( '.wp-lightbox-navigation-button-next' ); + var prevButton = overlay.querySelector( '.wp-lightbox-navigation-button-prev' ); + var maxCaptionBottom = window.innerHeight - bottomPadding; + var nextRect = nextButton ? nextButton.getBoundingClientRect() : null; + var prevRect = prevButton ? prevButton.getBoundingClientRect() : null; + var isPortraitOrNarrow = isPortraitOrNarrowImage( overlay, img ); + var overflow; + + if ( isPortraitOrNarrow ) { + if ( window.innerWidth <= 480 ) { + minContainerScale = 0.2; + } else if ( window.innerWidth <= 960 || window.innerHeight <= 700 ) { + minContainerScale = 0.24; + } else { + minContainerScale = 0.28; + } + } else if ( window.innerWidth <= 480 ) { + minContainerScale = 0.28; + } else if ( window.innerWidth <= 960 || window.innerHeight <= 700 ) { + minContainerScale = 0.35; + } + + overflow = Math.max( + 0, + caption.getBoundingClientRect().bottom - maxCaptionBottom + ); + + if ( rectsOverlap( caption.getBoundingClientRect(), nextRect ) ) { + overflow = Math.max( + overflow, + caption.getBoundingClientRect().bottom - ( nextRect.top - 12 ) + ); + } + + if ( rectsOverlap( caption.getBoundingClientRect(), prevRect ) ) { + overflow = Math.max( + overflow, + caption.getBoundingClientRect().bottom - ( prevRect.top - 12 ) + ); + } + + while ( overflow > 0 && attempt < 8 ) { + var currentContainerWidth = getOverlayMetric( overlay, '--wp--lightbox-container-width' ); + var currentContainerHeight = getOverlayMetric( overlay, '--wp--lightbox-container-height' ); + var currentImageWidth = getOverlayMetric( overlay, '--wp--lightbox-image-width' ); + var currentImageHeight = getOverlayMetric( overlay, '--wp--lightbox-image-height' ); + var currentLightboxScale = getOverlayMetric( overlay, '--wp--lightbox-scale' ); + var minContainerHeight = parseFloat( overlay.dataset.wporgLightboxContainerHeight ) * minContainerScale; + var newContainerHeight = Math.max( + minContainerHeight, + currentContainerHeight - ( overflow * 2 ) + ); + var scale; + + if ( + ! currentContainerWidth || + ! currentContainerHeight || + ! currentImageWidth || + ! currentImageHeight || + ! currentLightboxScale || + newContainerHeight === currentContainerHeight + ) { + return; + } + + scale = newContainerHeight / currentContainerHeight; + + overlay.style.setProperty( + '--wp--lightbox-container-width', + ( currentContainerWidth * scale ) + 'px' + ); + overlay.style.setProperty( + '--wp--lightbox-container-height', + newContainerHeight + 'px' + ); + overlay.style.setProperty( + '--wp--lightbox-image-width', + ( currentImageWidth * scale ) + 'px' + ); + overlay.style.setProperty( + '--wp--lightbox-image-height', + ( currentImageHeight * scale ) + 'px' + ); + overlay.style.setProperty( + '--wp--lightbox-scale', + currentLightboxScale / scale + ); + + overflow = Math.max( + 0, + caption.getBoundingClientRect().bottom - maxCaptionBottom + ); + + if ( rectsOverlap( caption.getBoundingClientRect(), nextRect ) ) { + overflow = Math.max( + overflow, + caption.getBoundingClientRect().bottom - ( nextRect.top - 12 ) + ); + } + + if ( rectsOverlap( caption.getBoundingClientRect(), prevRect ) ) { + overflow = Math.max( + overflow, + caption.getBoundingClientRect().bottom - ( prevRect.top - 12 ) + ); + } + + attempt++; + } + } + + /** + * Keeps portrait and very narrow screenshots readable by letting a + * text-heavy caption grow wider than the rendered image. + * + * Short labels stay aligned to the image width. Longer copy can use a + * wider bar when the screenshot itself is too narrow to carry a + * paragraph-shaped caption comfortably. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element. + * @param {HTMLImageElement|null} img The active lightbox image. + * @param {string} text The caption text. + * @param {boolean} promoteForLongCaption Whether a measured paragraph + * caption should get extra width. + */ + function applyCaptionWidth( overlay, caption, img, text, promoteForLongCaption ) { + var imageMetrics = getRenderedImageMetrics( overlay, img ); + var imageWidth = imageMetrics.width; + var viewportWidth = Math.max( 0, window.innerWidth - 16 ); + var isPortraitOrNarrow = isPortraitOrNarrowImage( overlay, img ); + var preferredWidth = imageWidth; + var relaxedWidth; + + if ( ! imageWidth || ! viewportWidth ) { + caption.style.removeProperty( '--wporg-lightbox-caption-width' ); + return; + } + + if ( isPortraitOrNarrow && ( text.length > 60 || promoteForLongCaption ) ) { + relaxedWidth = Math.max( + imageWidth, + Math.min( 420, imageWidth * 2 ), + 300 + ); + + if ( promoteForLongCaption ) { + relaxedWidth = Math.max( + relaxedWidth, + Math.min( 460, imageWidth * 2.3 ), + 340 + ); + } + + preferredWidth = Math.min( viewportWidth, relaxedWidth ); + } + + caption.style.setProperty( '--wporg-lightbox-caption-width', preferredWidth + 'px' ); + } + + /** + * Syncs caption width and long-text detection against the current + * rendered image size. + * + * The image may shrink after viewport-fitting, so this pass can be + * repeated after geometry changes to keep the final caption width in + * step with the visible screenshot. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {HTMLElement} caption The figcaption element. + * @param {HTMLImageElement|null} img The active lightbox image. + * @param {string} text The caption text. + */ + function syncCaptionPresentation( overlay, caption, img, text ) { + var isLongCaption; + + caption.classList.remove( 'is-long-caption' ); + applyCaptionWidth( overlay, caption, img, text, false ); + + isLongCaption = isParagraphCaption( text, caption ); + if ( isLongCaption ) { + applyCaptionWidth( overlay, caption, img, text, true ); + isLongCaption = isParagraphCaption( text, caption ); + } + + caption.classList.toggle( 'is-long-caption', isLongCaption ); + } + + /** + * Decides whether the caption should be treated as long-form copy. + * + * Short product-style captions keep the same centered treatment even if a + * narrow mobile viewport wraps them. Longer descriptions still keep that + * centered treatment, but we track them separately for width heuristics. + * + * @param {string} text The caption text. + * @param {HTMLElement} caption The figcaption element. + * @return {boolean} + */ + function isParagraphCaption( text, caption ) { + var renderedLineCount = getRenderedLineCount( caption ); + + return renderedLineCount > 2.5 || ( text.length > 220 && renderedLineCount > 2 ); + } + + /** + * Reveals the caption after core's opening zoom has finished. + * + * Geometry is applied before the first visible zoom frame so the image + * does not jump at the end of the animation. Text waits until the zoom + * completes, keeping the opening motion focused on the screenshot. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function scheduleDeferredSync( overlay ) { + clearDeferredSync( overlay ); + + if ( ! isOverlayActive( overlay ) ) { + return; + } + + overlay.wporgCaptionSyncTimer = window.setTimeout( function () { + overlay.wporgCaptionSyncTimer = null; + scheduleCaptionReveal( overlay, ensureCaption( overlay ) ); + }, CAPTION_REVEAL_DELAY ); + } + + /** + * Queues a two-phase sync so captions do not mutate the lightbox geometry + * while core is still animating the current image. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function requestSettledSync( overlay ) { + var shouldSyncImmediately = isOverlaySettled( overlay ); + + if ( ! isOverlayActive( overlay ) ) { + clearDeferredSync( overlay ); + markOverlayUnsettled( overlay ); + clearOverlayMetricCache( overlay ); + hideCaption( overlay, ensureCaption( overlay ) ); + return; + } + + if ( ! isManagedOverlay( overlay ) ) { + clearDeferredSync( overlay ); + markOverlayUnsettled( overlay ); + clearOverlayMetricCache( overlay ); + + if ( overlay.querySelector( 'figcaption.wp-lightbox-caption' ) ) { + hideCaption( overlay, ensureCaption( overlay ) ); + } + + return; + } + + if ( shouldSyncImmediately ) { + clearDeferredSync( overlay ); + clearCaptionReveal( overlay ); + clearOverlayMetricCache( overlay ); + syncCaption( overlay, true ); + return; + } + + markOverlaySettled( overlay ); + clearOverlayMetricCache( overlay ); + syncCaption( overlay, false ); + scheduleDeferredSync( overlay ); } /** * Returns the overlay's caption node, creating it on first call. * * The caption lives at the overlay level so it survives container - * shuffles between open / close. It is moved into whichever - * container is visible at sync time. + * shuffles between open / close and can be positioned outside the + * image frame. * * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. * @return {HTMLElement} @@ -95,21 +714,86 @@ } /** - * Reads the active image's alt text and writes it into the caption, - * mounted under the visible container. + * Reads the active image's alt text and writes it into the caption + * bar below the visible image container. * * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @param {boolean} keepReady Whether an already-visible caption should stay visible. */ - function syncCaption( overlay ) { - var caption = ensureCaption( overlay ); + function syncCaption( overlay, keepReady ) { var target = getVisibleContainer( overlay ); - moveCaptionInto( target, caption ); - // Read alt off the visible image (the off-screen sibling holds stale state). var img = target ? target.querySelector( 'img' ) : null; var text = img ? img.getAttribute( 'alt' ) || '' : ''; + var caption; + + if ( ! isOverlayActive( overlay ) ) { + clearDeferredSync( overlay ); + markOverlayUnsettled( overlay ); + clearOverlayMetricCache( overlay ); + caption = overlay.querySelector( 'figcaption.wp-lightbox-caption' ); + + if ( caption ) { + hideCaption( overlay, caption ); + } + + return; + } + + if ( ! isManagedImage( img ) ) { + clearDeferredSync( overlay ); + markOverlayUnsettled( overlay ); + clearOverlayMetricCache( overlay ); + caption = overlay.querySelector( 'figcaption.wp-lightbox-caption' ); + + if ( caption ) { + hideCaption( overlay, caption ); + } + + return; + } + + caption = ensureCaption( overlay ); + + if ( ! isOverlayActive( overlay ) ) { + clearDeferredSync( overlay ); + markOverlayUnsettled( overlay ); + clearOverlayMetricCache( overlay ); + hideCaption( overlay, caption ); + return; + } + + moveCaptionIntoOverlay( overlay, caption ); caption.textContent = text; - caption.classList.toggle( 'has-caption', text !== '' ); + + if ( text === '' ) { + hideCaption( overlay, caption ); + return; + } + + caption.classList.add( 'has-caption' ); + if ( ! keepReady ) { + caption.classList.remove( 'is-ready' ); + } + + if ( ! isOverlaySettled( overlay ) ) { + return; + } + + if ( overlay.dataset.wporgLightboxGeometryApplied !== '1' ) { + cacheOverlayMetrics( overlay, img ); + resetOverlayMetrics( overlay ); + syncCaptionPresentation( overlay, caption, img, text ); + fitCaptionWithinViewport( overlay, caption, img ); + syncCaptionPresentation( overlay, caption, img, text ); + overlay.dataset.wporgLightboxGeometryApplied = '1'; + } + + syncCaptionPresentation( overlay, caption, img, text ); + + if ( keepReady ) { + caption.classList.add( 'is-ready' ); + } } /** @@ -121,7 +805,8 @@ return; } - syncCaption( overlay ); + markOverlayUnsettled( overlay ); + requestSettledSync( overlay ); /* * Watch every image inside the overlay; core flips alt/src on @@ -130,7 +815,7 @@ var imgs = overlay.querySelectorAll( '.lightbox-image-container img' ); imgs.forEach( function ( img ) { var observer = new MutationObserver( function () { - syncCaption( overlay ); + requestSettledSync( overlay ); } ); observer.observe( img, { attributes: true, attributeFilter: [ 'alt', 'src' ] } ); } ); @@ -140,9 +825,24 @@ * transitions can move which container is on screen). */ var classObserver = new MutationObserver( function () { - syncCaption( overlay ); + if ( isOverlayActive( overlay ) ) { + requestSettledSync( overlay ); + return; + } + + requestSettledSync( overlay ); } ); classObserver.observe( overlay, { attributes: true, attributeFilter: [ 'class' ] } ); + + window.addEventListener( 'resize', function () { + requestSettledSync( overlay ); + } ); + + if ( window.visualViewport ) { + window.visualViewport.addEventListener( 'resize', function () { + requestSettledSync( overlay ); + } ); + } } if ( document.readyState === 'loading' ) { diff --git a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css index 6c221657c9..f7b8dd1ba3 100644 --- a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css +++ b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css @@ -96,6 +96,11 @@ .wp-block-gallery.has-nested-images.is-style-masonry figure.wp-block-image:not(#individual-image) button.lightbox-trigger { cursor: zoom-in; + /* Limit hover work to opacity so tall masonry tiles do not visually + twitch when the trigger fades in. */ + -webkit-backdrop-filter: none; + backdrop-filter: none; + will-change: opacity; } /** diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/css/screenshots.css b/wordpress.org/public_html/wp-content/plugins/plugin-directory/css/screenshots.css index 418085e036..95bade1d0c 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/css/screenshots.css +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/css/screenshots.css @@ -217,6 +217,12 @@ #screenshots .wp-block-gallery.is-style-screenshots figure.wp-block-image button.lightbox-trigger { cursor: zoom-in; + /* Core's frosted trigger repaints the underlying thumbnail on hover. + Keep the control, but make it a plain opacity change for smoother + screenshot-grid interaction. */ + -webkit-backdrop-filter: none; + backdrop-filter: none; + will-change: opacity; } /** From 164c0cff755899db964de951541060e01215ca22 Mon Sep 17 00:00:00 2001 From: Dan Zakirov Date: Sun, 14 Jun 2026 21:19:22 +0300 Subject: [PATCH 3/3] FIX: improved lightbox captions - Moved captions into a separate area below the image - Removed the dark gradient caption overlay from images - Changed caption text to the standard dark page color - Reserved caption space before final image sizing - Kept tall screenshots and long captions within the viewport - Added a subtle caption reveal animation on lightbox open - Reduced extra layout recalculations during the opening transition - Preserved Gallery block output, navigation, image sources, dimensions, layout detection, and cache behavior Affected: wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css, wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js --- .../assets/lightbox-captions.css | 3 +- .../assets/lightbox-captions.js | 80 ++++++++++++++++++- .../class-plugin-directory.php | 1 - 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css index 2fa776962e..2883d85a46 100644 --- a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css +++ b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css @@ -49,10 +49,9 @@ border: 0; border-radius: 12px; overflow-wrap: anywhere; - text-wrap: pretty; pointer-events: none; opacity: 0; - transition: opacity 120ms ease; + transition: opacity 180ms ease-out; } .wp-lightbox-overlay.active > figcaption.wp-lightbox-caption.has-caption.is-ready { diff --git a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js index 2878581f10..b5cf1df2ad 100644 --- a/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js +++ b/wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js @@ -14,7 +14,7 @@ */ ( function () { - var CAPTION_REVEAL_DELAY = 460; + var CAPTION_REVEAL_DELAY = 430; var SCREENSHOT_ID_OFFSET = 9000000; /** @@ -56,7 +56,7 @@ return containers[ i ]; } } - // Fallback: last container — wrong-spot caption beats no caption at all. + // Fallback to the last container because a misplaced caption is better than none. return containers[ containers.length - 1 ] || null; } @@ -124,6 +124,26 @@ return isManagedImage( img ); } + /** + * Returns a stable key for the active managed screenshot. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @return {string} + */ + function getActiveImageKey( overlay ) { + var target = getVisibleContainer( overlay ); + var img = target ? target.querySelector( 'img' ) : null; + + if ( ! isManagedImage( img ) ) { + return ''; + } + + return [ + getImageBlockId( img ), + img.currentSrc || img.src || img.getAttribute( 'src' ) || '', + ].join( '|' ); + } + /** * Whether the lightbox overlay is currently active. * @@ -162,6 +182,36 @@ overlay.dataset.wporgCaptionSettled = '1'; } + /** + * Returns whether the first caption reveal is still waiting for core zoom. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + * @return {boolean} + */ + function isCaptionRevealPending( overlay ) { + return overlay.dataset.wporgCaptionRevealPending === '1'; + } + + /** + * Marks the caption as waiting for the first reveal. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function markCaptionRevealPending( overlay ) { + overlay.dataset.wporgCaptionRevealPending = '1'; + overlay.dataset.wporgCaptionRevealKey = getActiveImageKey( overlay ); + } + + /** + * Clears the first reveal waiting state. + * + * @param {HTMLElement} overlay The `.wp-lightbox-overlay` element. + */ + function clearCaptionRevealPending( overlay ) { + delete overlay.dataset.wporgCaptionRevealPending; + delete overlay.dataset.wporgCaptionRevealKey; + } + /** * Clears cached core lightbox dimensions so the next settled sync starts * from the current image rather than a stale transition frame. @@ -200,6 +250,8 @@ window.clearTimeout( overlay.wporgCaptionRevealTimer ); overlay.wporgCaptionRevealTimer = null; } + + clearCaptionRevealPending( overlay ); } /** @@ -218,8 +270,11 @@ return; } + markCaptionRevealPending( overlay ); + overlay.wporgCaptionRevealTimer = window.setTimeout( function () { overlay.wporgCaptionRevealTimer = null; + clearCaptionRevealPending( overlay ); if ( ! isOverlayActive( overlay ) || ! caption.classList.contains( 'has-caption' ) ) { return; @@ -644,8 +699,13 @@ return; } + markCaptionRevealPending( overlay ); + overlay.wporgCaptionSyncTimer = window.setTimeout( function () { overlay.wporgCaptionSyncTimer = null; + markOverlaySettled( overlay ); + clearOverlayMetricCache( overlay ); + syncCaption( overlay, false ); scheduleCaptionReveal( overlay, ensureCaption( overlay ) ); }, CAPTION_REVEAL_DELAY ); } @@ -680,6 +740,21 @@ } if ( shouldSyncImmediately ) { + if ( isCaptionRevealPending( overlay ) ) { + var pendingKey = overlay.dataset.wporgCaptionRevealKey || ''; + var currentKey = getActiveImageKey( overlay ); + + clearOverlayMetricCache( overlay ); + syncCaption( overlay, false ); + + if ( currentKey !== pendingKey ) { + clearCaptionReveal( overlay ); + scheduleDeferredSync( overlay ); + } + + return; + } + clearDeferredSync( overlay ); clearCaptionReveal( overlay ); clearOverlayMetricCache( overlay ); @@ -687,7 +762,6 @@ return; } - markOverlaySettled( overlay ); clearOverlayMetricCache( overlay ); syncCaption( overlay, false ); scheduleDeferredSync( overlay ); diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php index 51e7a9dcbf..5a33a8bb26 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php @@ -468,7 +468,6 @@ public function register_shortcodes() { add_shortcode( Shortcodes\Release_Confirmation::SHORTCODE, array( __NAMESPACE__ . '\Shortcodes\Release_Confirmation', 'display' ) ); add_action( 'template_redirect', array( __NAMESPACE__ . '\Shortcodes\Release_Confirmation', 'template_redirect' ) ); - } /**