From 1ba4a13353a6150632537ea5d74d7ea8fd706e82 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 17 May 2026 17:33:06 +0200 Subject: [PATCH] fix: show "no body content" instead of infinite skeleton for bodyless emails --- components/email/email-list-item.tsx | 2 +- components/email/email-viewer.tsx | 9 ++++++--- components/email/thread-conversation-view.tsx | 2 +- components/email/thread-email-item.tsx | 4 +++- components/email/thread-list-item.tsx | 6 ++++-- locales/en/common.json | 2 ++ 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/components/email/email-list-item.tsx b/components/email/email-list-item.tsx index b625e383..6ea316b0 100644 --- a/components/email/email-list-item.tsx +++ b/components/email/email-list-item.tsx @@ -296,7 +296,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl ? "text-muted-foreground" : "text-muted-foreground/80" )}> - {trimmedPreview || "No preview available"} + {trimmedPreview || t('no_preview_available')}
)} > diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index d3c40e1a..f738ff4c 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -2489,7 +2489,7 @@ export function EmailViewer({ } return { - html: 'No content available
', + html: `${t('no_body_content')}
`, isHtml: false, hasStyleTag: false, }; @@ -2497,7 +2497,7 @@ export function EmailViewer({ // toggling permission imperatively unblocks content via restoreBlockedContent // in an effect below, so the iframe srcDoc stays stable and doesn't reload/flash. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [email, externalContentPolicy, cidBlobUrls]); + }, [email, externalContentPolicy, cidBlobUrls, t]); // Override email content with S/MIME decrypted content when available const effectiveEmailContent = useMemo(() => { @@ -2862,7 +2862,10 @@ export function EmailViewer({ // True while the new email's body is still being fetched. Catches the // window between selectedEmail changing and isLoading flipping true, so the // quick reply / body don't flicker through a partial render. - const isBodyLoading = isLoading || !email?.bodyValues || Object.keys(email.bodyValues).length === 0; + // An empty bodyValues with no referenced parts means the email has no body + // (e.g. calendar-only invites) — not "still loading". + const hasBodyParts = (email?.textBody?.length ?? 0) > 0 || (email?.htmlBody?.length ?? 0) > 0; + const isBodyLoading = isLoading || (hasBodyParts && (!email?.bodyValues || Object.keys(email.bodyValues).length === 0)); // Gates the quick reply on the iframe having loaded the current srcDoc, so // it doesn't flash in below a still-resizing iframe. diff --git a/components/email/thread-conversation-view.tsx b/components/email/thread-conversation-view.tsx index 9073eedb..1b82dfcb 100644 --- a/components/email/thread-conversation-view.tsx +++ b/components/email/thread-conversation-view.tsx @@ -526,7 +526,7 @@ function EmailCard({ {!isExpanded && density !== 'extra-compact' && (- {email.preview || "No preview available"} + {email.preview || t('email_viewer.no_preview_available')}
)} diff --git a/components/email/thread-email-item.tsx b/components/email/thread-email-item.tsx index 05933a3d..bcc25b46 100644 --- a/components/email/thread-email-item.tsx +++ b/components/email/thread-email-item.tsx @@ -1,6 +1,7 @@ "use client"; import { useCallback } from "react"; +import { useTranslations } from "next-intl"; import { formatDate } from "@/lib/utils"; import { Email } from "@/lib/jmap/types"; import { cn } from "@/lib/utils"; @@ -27,6 +28,7 @@ export function ThreadEmailItem({ onClick, onContextMenu, }: ThreadEmailItemProps) { + const t = useTranslations('email_viewer'); const isUnread = !email.keywords?.$seen; const isStarred = email.keywords?.$flagged; const isAnswered = email.keywords?.$answered; @@ -177,7 +179,7 @@ export function ThreadEmailItem({ ? "text-muted-foreground" : "text-muted-foreground/70" )}> - {email.preview || "No preview"} + {email.preview || t('no_preview_available')} {/* Date */} diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index 3cb26be5..34f67d5c 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -52,6 +52,7 @@ interface SingleEmailItemProps { const SingleEmailItem = React.forwardRef