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}
);
}