77 lines
2.3 KiB
TypeScript
77 lines
2.3 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.getAttribute("alt") || 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.alt || 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);
|
|
}, []);
|
|
|
|
if (!src || broken) {
|
|
return (
|
|
<span className="md-img-fallback" role="img" aria-label={alt || "图片加载失败"}>
|
|
{alt || "图片加载失败"}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
{...rest}
|
|
src={src}
|
|
alt={alt || ""}
|
|
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))
|
|
}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|