"use client"; import { useState, type ReactNode } from "react"; import { isRetiredStorageUrl } from "@/lib/storage/retired-storage"; type HomeImageProps = { sources: Array; alt: string; className?: string; imageClassName?: string; fallback?: ReactNode; loading?: "lazy" | "eager"; onUnavailable?: () => void; }; function imageCandidates(source: string) { // These two domains are documented aliases for the same gongxiangpan store. // Do not rewrite other buckets, providers, signed URLs or user-supplied hosts. try { const url = new URL(source); if (url.origin === "https://s3.yydd.ai" && url.pathname.startsWith("/gongxiangpan/") && !url.search && !url.hash) { return [`https://s3.jiansun.vip${url.pathname}`, source]; } } catch { /* Relative site assets need no host rewrite. */ } return [source]; } /** Keep failed image elements out of view, trying each distinct source once. */ export function HomeImage(props: HomeImageProps) { const sources = [...new Set(props.sources .filter((url): url is string => Boolean(url?.trim()) && !isRetiredStorageUrl(url)) .flatMap(imageCandidates))]; return ; } function HomeImageAttempt({ sources, alt, className, imageClassName, fallback, loading = "lazy", onUnavailable, }: Omit & { sources: string[] }) { const [index, setIndex] = useState(0); const [loaded, setLoaded] = useState(false); const source = sources[index]; return ( {!loaded && ( {fallback} )} {source ? ( {alt} setLoaded(true)} onError={() => { setLoaded(false); setIndex(index + 1); if (index + 1 >= sources.length) onUnavailable?.(); }} /> ) : null} ); }