Files
jiang13-bbs/frontend/components/markdown/MdImage.tsx
freefire 40bbad2c82 公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。
后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 00:11:24 +08:00

81 lines
2.5 KiB
TypeScript

"use client";
import { useState, useCallback, type ImgHTMLAttributes } from "react";
import ImageLightbox, { type LightboxItem } from "./ImageLightbox";
type MdImageProps = ImgHTMLAttributes<HTMLImageElement>;
function collectGallery(from: HTMLImageElement): { items: LightboxItem[]; index: number } {
const root = from.closest(".prose-content");
const nodes = root
? Array.from(root.querySelectorAll<HTMLImageElement>("img.md-content-img[src]"))
: [from];
const items: LightboxItem[] = nodes
.map((el) => ({
src: el.currentSrc || el.getAttribute("src") || "",
alt: el.dataset.caption || undefined,
}))
.filter((it) => !!it.src);
const selfSrc = from.currentSrc || from.getAttribute("src") || "";
let index = items.findIndex((it) => it.src === selfSrc);
if (index < 0) index = 0;
if (items.length === 0 && selfSrc) {
return { items: [{ src: selfSrc, alt: from.dataset.caption || undefined }], index: 0 };
}
return { items, index };
}
/** Markdown 正文图片:点击进入图箱,同文多图可切换 */
export default function MdImage({ src, alt, title, className, ...rest }: MdImageProps) {
const [broken, setBroken] = useState(false);
const [lightbox, setLightbox] = useState<{
items: LightboxItem[];
index: number;
} | null>(null);
const open = useCallback((el: HTMLImageElement) => {
const gallery = collectGallery(el);
if (gallery.items.length === 0) return;
setLightbox(gallery);
}, []);
const provided = (typeof alt === "string" ? alt.trim() : "") || (typeof title === "string" ? title.trim() : "");
const imgAlt = provided || "图片";
if (!src || broken) {
return (
<span className="md-img-fallback" role="img" aria-label={provided || "图片加载失败"}>
{provided || "图片加载失败"}
</span>
);
}
return (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
{...rest}
src={src}
alt={imgAlt}
data-caption={provided || undefined}
title={title}
className={`md-content-img${className ? ` ${className}` : ""}`}
loading="lazy"
decoding="async"
onClick={(e) => open(e.currentTarget)}
onError={() => setBroken(true)}
/>
{lightbox && (
<ImageLightbox
items={lightbox.items}
index={lightbox.index}
onClose={() => setLightbox(null)}
onIndexChange={(i) =>
setLightbox((prev) => (prev ? { ...prev, index: i } : prev))
}
/>
)}
</>
);
}