fix: show "no body content" instead of infinite skeleton for bodyless emails
This commit is contained in:
@@ -296,7 +296,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
|
|||||||
? "text-muted-foreground"
|
? "text-muted-foreground"
|
||||||
: "text-muted-foreground/80"
|
: "text-muted-foreground/80"
|
||||||
)}>
|
)}>
|
||||||
{trimmedPreview || "No preview available"}
|
{trimmedPreview || t('no_preview_available')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -2489,7 +2489,7 @@ export function EmailViewer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
html: '<p style="color: var(--color-muted-foreground);">No content available</p>',
|
html: `<p style="color: var(--color-muted-foreground); font-style: italic;">${t('no_body_content')}</p>`,
|
||||||
isHtml: false,
|
isHtml: false,
|
||||||
hasStyleTag: false,
|
hasStyleTag: false,
|
||||||
};
|
};
|
||||||
@@ -2497,7 +2497,7 @@ export function EmailViewer({
|
|||||||
// toggling permission imperatively unblocks content via restoreBlockedContent
|
// toggling permission imperatively unblocks content via restoreBlockedContent
|
||||||
// in an effect below, so the iframe srcDoc stays stable and doesn't reload/flash.
|
// in an effect below, so the iframe srcDoc stays stable and doesn't reload/flash.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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
|
// Override email content with S/MIME decrypted content when available
|
||||||
const effectiveEmailContent = useMemo(() => {
|
const effectiveEmailContent = useMemo(() => {
|
||||||
@@ -2862,7 +2862,10 @@ export function EmailViewer({
|
|||||||
// True while the new email's body is still being fetched. Catches the
|
// True while the new email's body is still being fetched. Catches the
|
||||||
// window between selectedEmail changing and isLoading flipping true, so the
|
// window between selectedEmail changing and isLoading flipping true, so the
|
||||||
// quick reply / body don't flicker through a partial render.
|
// 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
|
// Gates the quick reply on the iframe having loaded the current srcDoc, so
|
||||||
// it doesn't flash in below a still-resizing iframe.
|
// it doesn't flash in below a still-resizing iframe.
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ function EmailCard({
|
|||||||
</div>
|
</div>
|
||||||
{!isExpanded && density !== 'extra-compact' && (
|
{!isExpanded && density !== 'extra-compact' && (
|
||||||
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||||
{email.preview || "No preview available"}
|
{email.preview || t('email_viewer.no_preview_available')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
import { Email } from "@/lib/jmap/types";
|
import { Email } from "@/lib/jmap/types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -27,6 +28,7 @@ export function ThreadEmailItem({
|
|||||||
onClick,
|
onClick,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
}: ThreadEmailItemProps) {
|
}: ThreadEmailItemProps) {
|
||||||
|
const t = useTranslations('email_viewer');
|
||||||
const isUnread = !email.keywords?.$seen;
|
const isUnread = !email.keywords?.$seen;
|
||||||
const isStarred = email.keywords?.$flagged;
|
const isStarred = email.keywords?.$flagged;
|
||||||
const isAnswered = email.keywords?.$answered;
|
const isAnswered = email.keywords?.$answered;
|
||||||
@@ -177,7 +179,7 @@ export function ThreadEmailItem({
|
|||||||
? "text-muted-foreground"
|
? "text-muted-foreground"
|
||||||
: "text-muted-foreground/70"
|
: "text-muted-foreground/70"
|
||||||
)}>
|
)}>
|
||||||
{email.preview || "No preview"}
|
{email.preview || t('no_preview_available')}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Date */}
|
{/* Date */}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ interface SingleEmailItemProps {
|
|||||||
|
|
||||||
const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||||
function SingleEmailItem({ email, selected, onClick, onContextMenu, showPreview, colorTag, onToggleStar, onMarkAsRead, onDelete, onArchive, onSetColorTag, onMarkAsSpam }, ref) {
|
function SingleEmailItem({ email, selected, onClick, onContextMenu, showPreview, colorTag, onToggleStar, onMarkAsRead, onDelete, onArchive, onSetColorTag, onMarkAsSpam }, ref) {
|
||||||
|
const t = useTranslations('email_viewer');
|
||||||
const isUnread = !email.keywords?.$seen;
|
const isUnread = !email.keywords?.$seen;
|
||||||
const isStarred = email.keywords?.$flagged;
|
const isStarred = email.keywords?.$flagged;
|
||||||
const isAnswered = email.keywords?.$answered;
|
const isAnswered = email.keywords?.$answered;
|
||||||
@@ -317,7 +318,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
|||||||
? "text-muted-foreground"
|
? "text-muted-foreground"
|
||||||
: "text-muted-foreground/80"
|
: "text-muted-foreground/80"
|
||||||
)}>
|
)}>
|
||||||
{trimmedPreview || "No preview available"}
|
{trimmedPreview || t('no_preview_available')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -360,6 +361,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
|||||||
onMarkAsSpam,
|
onMarkAsSpam,
|
||||||
}, ref) {
|
}, ref) {
|
||||||
const t = useTranslations('threads');
|
const t = useTranslations('threads');
|
||||||
|
const tEmailViewer = useTranslations('email_viewer');
|
||||||
const showPreview = useSettingsStore((state) => state.showPreview);
|
const showPreview = useSettingsStore((state) => state.showPreview);
|
||||||
const density = useSettingsStore((state) => state.density);
|
const density = useSettingsStore((state) => state.density);
|
||||||
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
||||||
@@ -724,7 +726,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
|||||||
? "text-muted-foreground"
|
? "text-muted-foreground"
|
||||||
: "text-muted-foreground/80"
|
: "text-muted-foreground/80"
|
||||||
)}>
|
)}>
|
||||||
{trimmedPreview || "No preview available"}
|
{trimmedPreview || tEmailViewer('no_preview_available')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -249,6 +249,8 @@
|
|||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"compose_hint": "Compose new message",
|
"compose_hint": "Compose new message",
|
||||||
"no_subject": "(No Subject)",
|
"no_subject": "(No Subject)",
|
||||||
|
"no_body_content": "(No body content available)",
|
||||||
|
"no_preview_available": "No preview available",
|
||||||
"loading_email": "Loading email...",
|
"loading_email": "Loading email...",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
|
|||||||
Reference in New Issue
Block a user