import { LoaderCircle } from "lucide-react"; import { DownloadCloudIcon } from "../../icons/DownloadCloudIcon"; import { useState } from "react"; import { useI18n } from "../../../i18n"; import type { Attachment, Post } from "../../../types/post"; import { downloadAttachment } from "../utils/downloadFile"; import { fileIcon } from "../utils/fileIcon"; import { filenameWithExtension, splitFilename } from "../utils/filenameDisplay"; import { formatBytes } from "../utils/formatBytes"; import { formatDateTime } from "../utils/formatTime"; import { postDisplayText } from "../utils/postText"; import { CollapsibleText } from "../CollapsibleText"; import { mediaSaveKindFromAttachment, useSaveToAlbumGuide, } from "../../SaveToAlbumGuide"; import { useToast } from "../../Toast"; import { FavoriteButton } from "../../../favorites/FavoriteButton"; import { BubbleAttachmentDownloadButton } from "../BubbleAttachmentDownloadButton"; import type { MessageBubbleVariant } from "../MessageBubble"; function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) { const { Icon, color } = fileIcon({ mime: att.mime, filename: att.filename }); const displayFilename = filenameWithExtension(att.filename, att.mime); const [previewFailed, setPreviewFailed] = useState(false); const isImage = att.kind === "image" || att.mime.startsWith("image/"); const previewUrl = att.thumbnailUrl ?? att.posterUrl ?? (isImage ? att.url : undefined); return (
{previewUrl && !previewFailed ? ( setPreviewFailed(true)} className="h-16 w-16 shrink-0 rounded-lg object-fill" /> ) : ( )}
{(() => { const { base, ext } = splitFilename(displayFilename); const tailChars = Math.min(4, base.length); const head = base.slice(0, base.length - tailChars); const tail = base.slice(base.length - tailChars) + ext; return ( <> {head} {tail} ); })()}
{formatBytes(att.sizeBytes)}
); } function LatestFileCard({ post }: { post: Post }) { const { t, lang } = useI18n(); const { showToast } = useToast(); const { showSaveToAlbumGuide } = useSaveToAlbumGuide(); const [isDownloading, setIsDownloading] = useState(false); const att = post.attachments[0]; const text = postDisplayText(post, lang); if (!att) return null; const { Icon, color } = fileIcon({ mime: att.mime, filename: att.filename }); const displayFilename = filenameWithExtension(att.filename, att.mime); const handleDownload = async () => { if (isDownloading) return; setIsDownloading(true); try { await downloadAttachment(post.id, att.id, displayFilename); const mediaKind = mediaSaveKindFromAttachment(att); if (mediaKind) showSaveToAlbumGuide(mediaKind); } catch { showToast(t("downloadFail"), "error"); } finally { setIsDownloading(false); } }; return (
{(() => { const { base, ext } = splitFilename(displayFilename); const tailChars = Math.min(8, base.length); const head = base.slice(0, base.length - tailChars); const tail = base.slice(base.length - tailChars) + ext; return ( <> {head} {tail} ); })()}
{isDownloading ? t("downloading") : formatBytes(att.sizeBytes)}
{text ? (
{text}
) : (
)}
); } export function FileDocBubble({ post, variant = "default", }: { post: Post; variant?: MessageBubbleVariant; }) { const { lang } = useI18n(); const text = postDisplayText(post, lang); if (variant === "latest") { return ; } return (
{post.attachments.map((att) => ( ))} {text ? ( {text} ) : null}
); }