"use client"; import { useState, useCallback, type ImgHTMLAttributes } from "react"; import ImageLightbox, { type LightboxItem } from "./ImageLightbox"; type MdImageProps = ImgHTMLAttributes; function collectGallery(from: HTMLImageElement): { items: LightboxItem[]; index: number } { const root = from.closest(".prose-content"); const nodes = root ? Array.from(root.querySelectorAll("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 || "图片"; // 抖音表情(/emoji/ 路径):行内小图,不进图箱、不带 caption; // alt 由 expandDyEmojiShortcodes 产出("dy:"),悬停 title 显示其短代码 const isEmoji = typeof src === "string" && src.includes("/emoji/"); const emojiSc = typeof alt === "string" && alt.startsWith("dy:") ? `[:${alt.slice(3)}:]` : undefined; if (isEmoji && !broken) { return ( /* eslint-disable-next-line @next/next/no-img-element */ {imgAlt} e.preventDefault()} onError={() => setBroken(true)} /> ); } if (!src || broken) { return ( {provided || "图片加载失败"} ); } return ( <> {/* eslint-disable-next-line @next/next/no-img-element */} {imgAlt} open(e.currentTarget)} onError={() => setBroken(true)} /> {lightbox && ( setLightbox(null)} onIndexChange={(i) => setLightbox((prev) => (prev ? { ...prev, index: i } : prev)) } /> )} ); }