import { ArrowDownToLine, LoaderCircle, X } from "lucide-react"; import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { useI18n } from "../../../i18n"; import type { Attachment, Post } from "../../../types/post"; import { AttachmentDownloadPill } from "../AttachmentDownloadPill"; import { useLightbox } from "../overlays/ImageLightbox"; import { autolink } from "../utils/autolink"; import { downloadAttachment } from "../utils/downloadFile"; import { formatBytes } from "../utils/formatBytes"; import { postDisplayText } from "../utils/postText"; const MAX_VISIBLE = 4; function imageRatio(att: Attachment) { return att.width && att.height ? `${att.width} / ${att.height}` : "4 / 3"; } function ImageListDownloadButton({ postId, attachment, }: { postId: string; attachment: Attachment; }) { const { t } = useI18n(); const [isDownloading, setIsDownloading] = useState(false); const handleDownload = () => { if (isDownloading) return; setIsDownloading(true); void downloadAttachment(postId, attachment.id, attachment.filename) .finally(() => setIsDownloading(false)) .catch(() => {}); }; return ( ); } function ImageListDialog({ postId, images, onClose, onPick, }: { postId: string; images: Attachment[]; onClose: () => void; onPick: (index: number) => void; }) { useEffect(() => { const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); return createPortal(
e.stopPropagation()} >
选择图片
{images.map((image, index) => (
))}
, document.body, ); } export function AlbumBubble({ post }: { post: Post }) { const { openLightbox } = useLightbox(); const { lang } = useI18n(); const [listOpen, setListOpen] = useState(false); const images = post.attachments; const text = postDisplayText(post, lang); const shouldMerge = images.length > MAX_VISIBLE; if (!shouldMerge) { return (
{images.map((att, i) => (
))} {text ? (
{autolink(text)}
) : null}
); } const visible = images.slice(0, MAX_VISIBLE); const extra = images.length - MAX_VISIBLE; return (
{visible.map((att, i) => { const isLastSlot = i === MAX_VISIBLE - 1 && extra > 0; return (
{!isLastSlot ? ( ) : null}
); })}
{text ? (
{autolink(text)}
) : null} {listOpen ? ( setListOpen(false)} onPick={(index) => { setListOpen(false); openLightbox(images, index, text, post.id); }} /> ) : null}
); }