s3.1080p.hk 已永久停用且文件不可恢复。首页查询排除旧源并统一分页统计,历史图册、聊天和画布保留记录并显示失效说明;下载和代理返回 410,停止旧源上传与归档。使用本地黑白 Logo 适配主题。 提供保存原配置后停用旧存储、取消失效精选、替换 Logo 的整理脚本。七牛 10 月 14 日上传核查记录见 docs/retired-storage.md。 验证:7 项旧存储检查、41 项下载和归档回归测试、TypeScript 检查通过。
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, type ReactNode } from "react";
|
|
import { isRetiredStorageUrl } from "@/lib/storage/retired-storage";
|
|
|
|
type HomeImageProps = {
|
|
sources: Array<string | null | undefined>;
|
|
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 <HomeImageAttempt key={JSON.stringify(sources)} {...props} sources={sources} />;
|
|
}
|
|
|
|
function HomeImageAttempt({
|
|
sources, alt, className, imageClassName, fallback, loading = "lazy", onUnavailable,
|
|
}: Omit<HomeImageProps, "sources"> & { sources: string[] }) {
|
|
const [index, setIndex] = useState(0);
|
|
const [loaded, setLoaded] = useState(false);
|
|
const source = sources[index];
|
|
return (
|
|
<span className={`relative block overflow-hidden ${className || ""}`}>
|
|
{!loaded && (
|
|
<span className={fallback ? "block" : "block aspect-[4/5] rounded-[12px] bg-[#e8e8e8] dark:bg-[#1b1c20]"}>
|
|
{fallback}
|
|
</span>
|
|
)}
|
|
{source ? (
|
|
<img
|
|
key={source}
|
|
src={source}
|
|
alt={alt}
|
|
loading={loading}
|
|
decoding="async"
|
|
className={`${imageClassName || ""} ${loaded ? "" : "absolute inset-0 opacity-0"}`}
|
|
onLoad={() => setLoaded(true)}
|
|
onError={() => {
|
|
setLoaded(false);
|
|
setIndex(index + 1);
|
|
if (index + 1 >= sources.length) onUnavailable?.();
|
|
}}
|
|
/>
|
|
) : null}
|
|
</span>
|
|
);
|
|
}
|