mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: show the preview on its own line again at the regular density
This commit is contained in:
@@ -54,38 +54,24 @@ beforeEach(() => {
|
||||
useEmailStore.setState({ selectedEmailKeys: new Set<string>(), selectedMailbox: 'inbox' });
|
||||
});
|
||||
|
||||
// mail-15: the default density puts the preview after the subject, on the
|
||||
// subject's line, so about ten rows fit at 1366×768 (they were about 96 px,
|
||||
// seven on screen); comfortable keeps two preview lines, and a phone keeps
|
||||
// the preview under the subject.
|
||||
// mail-15: the default density shows one preview line, so more rows fit;
|
||||
// comfortable keeps two.
|
||||
describe('list row preview lines', () => {
|
||||
const onSubjectLine = () => {
|
||||
const preview = screen.getByText(PREVIEW, { exact: false });
|
||||
return preview.closest('[data-row-preview]') !== null;
|
||||
};
|
||||
it.each([
|
||||
['regular', 'line-clamp-1'],
|
||||
['comfortable', 'line-clamp-2'],
|
||||
] as const)('a %s row clamps its preview with %s', (density, clamp) => {
|
||||
useSettingsStore.setState({ density });
|
||||
renderRow([makeEmail()]);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain(clamp);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['message', [makeEmail()]],
|
||||
['conversation', [makeEmail({ id: 'a' }), makeEmail({ id: 'b', receivedAt: '2024-01-16T10:00:00Z' })]],
|
||||
] as const)('a regular %s row shows its preview on the subject line', (_kind, emails) => {
|
||||
useSettingsStore.setState({ density: 'regular' });
|
||||
renderRow([...emails]);
|
||||
expect(onSubjectLine()).toBe(true);
|
||||
expect(document.querySelector('p.line-clamp-1')).toBeNull();
|
||||
});
|
||||
|
||||
it('a comfortable row keeps two preview lines of its own', () => {
|
||||
useSettingsStore.setState({ density: 'comfortable' });
|
||||
renderRow([makeEmail()]);
|
||||
expect(onSubjectLine()).toBe(false);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain('line-clamp-2');
|
||||
});
|
||||
|
||||
it('a regular row on a phone keeps the preview under the subject', () => {
|
||||
useUIStore.setState({ isMobile: true });
|
||||
useSettingsStore.setState({ density: 'regular' });
|
||||
renderRow([makeEmail()]);
|
||||
expect(onSubjectLine()).toBe(false);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain('line-clamp-1');
|
||||
['regular', 'line-clamp-1'],
|
||||
['comfortable', 'line-clamp-2'],
|
||||
] as const)('a %s conversation row clamps its preview with %s', (density, clamp) => {
|
||||
useSettingsStore.setState({ density });
|
||||
renderRow([makeEmail({ id: 'a' }), makeEmail({ id: 'b', receivedAt: '2024-01-16T10:00:00Z' })]);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain(clamp);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,9 +93,7 @@ describe('message list rows under the bottom reading pane (#262)', () => {
|
||||
it('keeps stacked rows in the split layout', () => {
|
||||
useSettingsStore.setState({ mailLayout: 'split' });
|
||||
const { container } = renderEmails([makeEmail()]);
|
||||
// Stacked: a preview line of its own, or the preview after the subject
|
||||
// on the subject's line at the regular density (mail-15).
|
||||
expect(container.querySelector('p, [data-row-preview]')).not.toBeNull();
|
||||
expect(container.querySelector('p')).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('as columns', () => {
|
||||
|
||||
@@ -39,7 +39,7 @@ import { useOpenEmailInWindow } from "@/hooks/use-popouts-available";
|
||||
import { useIsDesktop } from "@/hooks/use-media-query";
|
||||
import { useIsEmbedded } from "@/hooks/use-is-embedded";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { useListRowShape, showsPreviewOnSubjectLine } from "@/hooks/use-list-row-shape";
|
||||
import { useListRowShape } from "@/hooks/use-list-row-shape";
|
||||
import { MailListRowsSkeleton, type MailRowShape } from "@/components/boot-skeleton/mail-skeleton";
|
||||
import type { Density } from "@/lib/density";
|
||||
import { useUIStore } from "@/stores/ui-store";
|
||||
@@ -492,11 +492,7 @@ export function EmailList({
|
||||
// up after a jump down (scrollbar drag, End). So guess per row.
|
||||
const latest = item?.kind === 'row' ? item.thread.latestEmail : undefined;
|
||||
let size = { 'extra-compact': 32, compact: 60, regular: 84, comfortable: 104 }[density];
|
||||
if (showsPreviewOnSubjectLine({ showPreview, density, isMobile, mountainView: mountainViewList })) {
|
||||
// The preview follows the subject on its line, and the subject line
|
||||
// drops its margin (mail-15).
|
||||
size -= 4;
|
||||
} else if (showPreview && density !== 'extra-compact') {
|
||||
if (showPreview && density !== 'extra-compact') {
|
||||
// A mail without a preview draws a one-line "No preview available",
|
||||
// a line (23px) shorter than a real one. Only comfortable rows show a
|
||||
// second preview line (mail-15).
|
||||
@@ -507,7 +503,7 @@ export function EmailList({
|
||||
const hasCode = !!latest && showVerificationCodes && !!listVerificationCode(latest);
|
||||
if (latest && (hasCode || (onOpenAttachment && listRowShowsChips(latest, loadAttachments)))) size += 28;
|
||||
return size;
|
||||
}, [density, isFocusedMailLayout, showPreview, showVerificationCodes, items, onOpenAttachment, loadAttachments, isMobile, mountainViewList]);
|
||||
}, [density, isFocusedMailLayout, showPreview, showVerificationCodes, items, onOpenAttachment, loadAttachments]);
|
||||
|
||||
// Stable per list, so the virtualizer does not rebuild every row's
|
||||
// measurement on each scroll render.
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Paperclip, Star, Pin, Circle, ChevronRight, ChevronDown, Loader2, Messa
|
||||
import type { AppIcon } from "@/components/icons";
|
||||
import { useSettingsStore, KEYWORD_PALETTE } from "@/stores/settings-store";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useListRowShape, LIST_COLUMN_CLASS, showsPreviewOnSubjectLine } from "@/hooks/use-list-row-shape";
|
||||
import { useListRowShape, LIST_COLUMN_CLASS } from "@/hooks/use-list-row-shape";
|
||||
import { SizeCell } from "./list-columns";
|
||||
import { accountTintKey, generateAvatarColor } from "@/lib/account-utils";
|
||||
import { useUIStore } from "@/stores/ui-store";
|
||||
@@ -499,7 +499,6 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
// together.
|
||||
const mvRow = interfaceLayout === 'mountain-view' && oneLineRow;
|
||||
const mvMobileRow = interfaceLayout === 'mountain-view' && isMobile;
|
||||
const previewOnSubjectLine = showsPreviewOnSubjectLine({ showPreview, density, isMobile, mountainView: interfaceLayout === 'mountain-view' });
|
||||
const trimmedPreview = cleanPreview(email.preview);
|
||||
const inlinePreview = showPreview && trimmedPreview ? ` ${trimmedPreview}` : '';
|
||||
// Search hits carry server snippets with the matched terms marked; they
|
||||
@@ -973,7 +972,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("flex min-w-0 items-center gap-1.5", !previewOnSubjectLine && "mb-1")}>
|
||||
<div className="mb-1 flex min-w-0 items-center gap-1.5">
|
||||
{tagPlacement === 'subject' && tagIds.length > 0 && (
|
||||
<span className={TAG_GROUP_CLASS}>
|
||||
{tagIds.map((id) => (
|
||||
@@ -988,16 +987,11 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
: "font-normal text-foreground/90"
|
||||
)}>
|
||||
<bdi>{subjectSnippet ? <SearchSnippetText snippet={subjectSnippet} /> : (email.subject || tNoSubject)}</bdi>
|
||||
{previewOnSubjectLine && (
|
||||
<span data-row-preview="" className="font-normal text-muted-foreground">
|
||||
{' – '}<span dir="auto">{previewSnippet ? <SearchSnippetText snippet={previewSnippet} /> : (trimmedPreview || t('no_preview_available'))}</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<PluginRowBadges email={email} />
|
||||
</div>
|
||||
|
||||
{showPreview && !previewOnSubjectLine && density !== 'extra-compact' && density !== 'compact' && (
|
||||
{showPreview && density !== 'extra-compact' && density !== 'compact' && (
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
@@ -1099,7 +1093,6 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
// together.
|
||||
const mvRow = interfaceLayout === 'mountain-view' && oneLineRow;
|
||||
const mvMobileRow = interfaceLayout === 'mountain-view' && isMobile;
|
||||
const previewOnSubjectLine = showsPreviewOnSubjectLine({ showPreview, density, isMobile, mountainView: interfaceLayout === 'mountain-view' });
|
||||
const trimmedPreview = cleanPreview(latestEmail.preview);
|
||||
const inlinePreview = showPreview && trimmedPreview ? ` ${trimmedPreview}` : '';
|
||||
// In a search the matched mail need not be the thread's latest one: show
|
||||
@@ -1767,7 +1760,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("flex min-w-0 items-center gap-1.5", !previewOnSubjectLine && "mb-1")}>
|
||||
<div className="mb-1 flex min-w-0 items-center gap-1.5">
|
||||
{tagPlacement === 'subject' && tagIds.length > 0 && (
|
||||
<span className={TAG_GROUP_CLASS}>
|
||||
{tagIds.map((id) => (
|
||||
@@ -1782,16 +1775,11 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
: "font-normal text-foreground/90"
|
||||
)}>
|
||||
<bdi>{subjectSnippet ? <SearchSnippetText snippet={subjectSnippet} /> : (latestEmail.subject || tNoSubject)}</bdi>
|
||||
{previewOnSubjectLine && (
|
||||
<span data-row-preview="" className="font-normal text-muted-foreground">
|
||||
{' – '}<span dir="auto">{previewSnippet ? <SearchSnippetText snippet={previewSnippet} /> : (trimmedPreview || tEmailViewer('no_preview_available'))}</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<PluginRowBadges email={latestEmail} />
|
||||
</div>
|
||||
|
||||
{showPreview && !previewOnSubjectLine && density !== 'extra-compact' && density !== 'compact' && (
|
||||
{showPreview && density !== 'extra-compact' && density !== 'compact' && (
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
|
||||
@@ -36,19 +36,3 @@ export const LIST_COLUMN_CLASS = {
|
||||
date: 'w-28 shrink-0 text-end',
|
||||
size: 'w-16 shrink-0 text-end',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Whether a stacked row puts its preview after the subject, on the subject's
|
||||
* line, instead of on a line of its own (mail-15). The regular density does
|
||||
* on a computer, which brings a row from about 96 px to about 68 px, so a
|
||||
* 1366×768 screen lists about ten. Comfortable keeps two preview lines, and
|
||||
* phones keep the preview under the subject, where the line is too short.
|
||||
*/
|
||||
export function showsPreviewOnSubjectLine(options: {
|
||||
showPreview: boolean;
|
||||
density: string;
|
||||
isMobile: boolean;
|
||||
mountainView: boolean;
|
||||
}): boolean {
|
||||
return options.showPreview && options.density === 'regular' && !options.isMobile && !options.mountainView;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user