'use client';
import { useEffect, useState } from 'react';
import Image from '../../embed-shims/next-image';
import { useRichMarkdownRuntime } from './rich-markdown-runtime';
/**
* In-article markdown image.
*
* Markdown images have unknown intrinsic dimensions — which is what broke the old
* approach: a fixed width/height made the Supabase loader `resize=cover` CROP tall
* screenshots, and `w-full` then blew them up to the column width. This renders
* through the lib's `next/image` shim (full Next.js Image Optimization on the hub,
* plain `
` everywhere else) and fixes both problems at the source:
*
* - `object-contain` flips the hub's Supabase loader (injected via
* `transformImageSrc` from the {@link RichMarkdownRuntimeProvider}) to
* `resize=contain` — aspect preserved, never cropped.
* - We learn the real aspect ratio from a tiny probe (the same
* `transformImageSrc` at 48px when available) and pass matching width/height
* so `next/image`'s aspect-ratio box is correct.
* - CSS (`max-w-full` + `max-h` + auto) caps the on-page size so a tall
* screenshot can't dominate the article, while the browser keeps the true
* aspect ratio.
*
* Embedders that don't pass `transformImageSrc` get an identity fallback — the
* raw `src` is used both for the probe and the display copy.
*/
const MAX_H_REM = 32; // ~512px on-page cap
const DISPLAY_W = 768; // logical width hint; the optimizer handles srcset + retina
export function MarkdownImage({ src, alt }: { src: string; alt?: string }) {
const { transformImageSrc } = useRichMarkdownRuntime();
const [ratio, setRatio] = useState(null); // width / height
useEffect(() => {
let cancelled = false;
// Reset on src change so a reused instance doesn't briefly size the new image with
// the previous image's ratio.
setRatio(null);
// Probe a tiny aspect-preserving variant so we learn the real ratio without
// downloading the full image; the display copy is then fetched once, at the right size.
// When no transformer is wired (embedders), fall back to the raw src.
const probeSrc = transformImageSrc(src, { width: 48, resize: 'contain', quality: 20 }) ?? src;
const probe = new window.Image();
probe.onload = () => {
if (!cancelled && probe.naturalWidth && probe.naturalHeight) {
setRatio(probe.naturalWidth / probe.naturalHeight);
}
};
probe.onerror = () => {
if (!cancelled) setRatio(1.5); // neutral fallback so we still render something
};
probe.src = probeSrc;
return () => {
cancelled = true;
};
}, [src, transformImageSrc]);
// Reserve a neutral box while probing so the layout doesn't jump when the image appears.
if (!ratio) {
return (
);
}
return (
);
}