mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: let senders, subjects and previews in the list and reader take their own text direction, so English text in Arabic is cut at its end
This commit is contained in:
@@ -875,3 +875,25 @@ describe('ThreadListItem swipe on a conversation', () => {
|
||||
expect(onArchive).toHaveBeenCalledWith(expect.objectContaining({ id: 'b' }));
|
||||
});
|
||||
});
|
||||
|
||||
// R11: in an Arabic interface an English subject was cut on the wrong side.
|
||||
// The text takes its direction from itself, wherever it sits.
|
||||
describe('ThreadListItem text direction', () => {
|
||||
beforeEach(() => {
|
||||
useSettingsStore.setState({ emailKeywords: [...DEFAULT_KEYWORDS], showPreview: true, mailLayout: 'split', interfaceLayout: 'default' });
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a single message', () => groupEmailsByThread([makeEmail({ preview: 'Preview text here' })])[0]],
|
||||
['a conversation', () => groupEmailsByThread([
|
||||
makeEmail({ id: 'a', receivedAt: '2024-01-15T10:00:00Z', preview: 'Preview text here' }),
|
||||
makeEmail({ id: 'b', receivedAt: '2024-01-15T11:00:00Z', preview: 'Preview text here' }),
|
||||
])[0]],
|
||||
])('gives the sender, subject and preview of %s dir="auto"', (_name, makeThread) => {
|
||||
render(<ThreadListItem thread={makeThread()} isExpanded={false} onToggleExpand={() => {}} onEmailSelect={() => {}} />);
|
||||
for (const text of ['Alice', 'Test Subject', 'Preview text here']) {
|
||||
const el = screen.getAllByText((_c, node) => node?.textContent?.trim() === text && node.children.length === 0)[0];
|
||||
expect(el.closest('[dir="auto"]'), text).not.toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -760,7 +760,7 @@ function MvConversationCard({ email, onOpen, timeFormat, compact }: { email: Ema
|
||||
<Avatar name={sender?.name} email={sender?.email} size="md" className="w-10 h-10 flex-shrink-0" />
|
||||
<span className="flex-1 min-w-0">
|
||||
<span className="flex items-baseline gap-2">
|
||||
<span className={cn("text-sm truncate", unread ? "font-bold" : "font-semibold")}>
|
||||
<span dir="auto" className={cn("text-sm truncate", unread ? "font-bold" : "font-semibold")}>
|
||||
{sender?.name || sender?.email || ''}
|
||||
</span>
|
||||
<span className="ms-auto ps-2 text-xs text-muted-foreground whitespace-nowrap">
|
||||
@@ -876,7 +876,7 @@ export function MvExpandedConversationCard({
|
||||
<Avatar name={sender?.name} email={sender?.email} size="md" className="w-10 h-10 flex-shrink-0" />
|
||||
<span className="flex-1 min-w-0">
|
||||
<span className="flex items-baseline gap-2">
|
||||
<span className="text-sm font-semibold truncate">{sender?.name || sender?.email || ''}</span>
|
||||
<span dir="auto" className="text-sm font-semibold truncate">{sender?.name || sender?.email || ''}</span>
|
||||
{!compact && sender?.name && sender.email && (
|
||||
<span className="text-xs text-muted-foreground truncate"><{sender.email}></span>
|
||||
)}
|
||||
|
||||
@@ -389,7 +389,7 @@ function EmailCard({
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
"font-medium truncate",
|
||||
isUnread ? "text-foreground" : "text-muted-foreground"
|
||||
)}>
|
||||
@@ -406,7 +406,7 @@ function EmailCard({
|
||||
{formatDate(emailDisplayDate(email))}
|
||||
</div>
|
||||
{!isExpanded && density !== 'extra-compact' && (
|
||||
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||
<p dir="auto" className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||
{cleanPreview(email.preview) || t('email_viewer.no_preview_available')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -675,7 +675,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
<span className="sr-only">{accountDescription}</span>
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir={columns ? undefined : "auto"} className={cn(
|
||||
columns ? cn(LIST_COLUMN_CLASS.from, 'flex items-center gap-2') : 'w-32 shrink-0 truncate lg:w-40',
|
||||
'text-sm',
|
||||
isUnread ? (mvRow ? 'font-bold text-foreground' : 'font-semibold text-foreground') : 'font-medium text-foreground/80'
|
||||
@@ -693,7 +693,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
<span className="sr-only">{accountDescription}</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="min-w-0 truncate">
|
||||
<span dir="auto" className="min-w-0 truncate">
|
||||
{sender?.name || (sender?.email && formatAddress(sender.email)) || tUnknown}
|
||||
</span>
|
||||
</>
|
||||
@@ -707,7 +707,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
'min-w-0 truncate',
|
||||
isUnread ? (mvRow ? 'font-bold text-foreground' : 'font-semibold text-foreground') : 'text-foreground/90'
|
||||
)}>
|
||||
@@ -715,7 +715,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
</span>
|
||||
<PluginRowBadges email={email} className={mvRow ? "ms-2" : undefined} />
|
||||
{(previewSnippet || inlinePreview) && (
|
||||
<span className="min-w-0 shrink-[9999] truncate text-muted-foreground">
|
||||
<span dir="auto" className="min-w-0 shrink-[9999] truncate text-muted-foreground">
|
||||
{/* Gmail runs the snippet on from the subject after a dash. */}
|
||||
{mvRow && '\u00a0-\u00a0'}
|
||||
{previewSnippet ? <> <SearchSnippetText snippet={previewSnippet} /></> : (mvRow ? trimmedPreview : inlinePreview)}
|
||||
@@ -773,7 +773,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
<span className="sr-only">{accountDescription}</span>
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
"truncate text-sm",
|
||||
isUnread
|
||||
? "font-bold text-foreground"
|
||||
@@ -850,7 +850,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
"min-w-0 flex-1 truncate text-sm",
|
||||
isUnread
|
||||
? "font-semibold text-foreground"
|
||||
@@ -863,7 +863,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
|
||||
{showPreview && density !== 'extra-compact' && density !== 'compact' && (
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p className={cn(
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : "line-clamp-2",
|
||||
isUnread
|
||||
@@ -1379,7 +1379,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
<span className="sr-only">{threadAccountDescription}</span>
|
||||
</span>
|
||||
)}
|
||||
<span className={cn('min-w-0 truncate', hasUnread ? 'font-semibold text-foreground' : 'font-medium text-foreground/80')}>
|
||||
<span dir="auto" className={cn('min-w-0 truncate', hasUnread ? 'font-semibold text-foreground' : 'font-medium text-foreground/80')}>
|
||||
{displayNames.join(', ')}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-muted-foreground" title={t('messages_tooltip', { count: emailCount })}>
|
||||
@@ -1389,7 +1389,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
) : mvRow ? (
|
||||
// Gmail puts the message count after the names, in grey.
|
||||
<span data-mv-names="" className="flex w-32 shrink-0 items-baseline gap-1 text-sm lg:w-40">
|
||||
<span className={cn('min-w-0 truncate', hasUnread ? 'font-bold text-foreground' : 'font-medium text-foreground/80')}>
|
||||
<span dir="auto" className={cn('min-w-0 truncate', hasUnread ? 'font-bold text-foreground' : 'font-medium text-foreground/80')}>
|
||||
{displayNames.join(', ')}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-muted-foreground" title={t('messages_tooltip', { count: emailCount })}>
|
||||
@@ -1397,7 +1397,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
// Matches SingleEmailItem: the sender column sets where
|
||||
// every row's tags and subject begin, so the two have to
|
||||
// agree or thread rows sit 1rem further right.
|
||||
@@ -1422,7 +1422,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
'min-w-0 truncate',
|
||||
hasUnread ? (mvRow ? 'font-bold text-foreground' : 'font-semibold text-foreground') : 'text-foreground/90'
|
||||
)}>
|
||||
@@ -1430,7 +1430,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
</span>
|
||||
<PluginRowBadges email={latestEmail} className={mvRow ? "ms-2" : undefined} />
|
||||
{(previewSnippet || inlinePreview) && (
|
||||
<span className="min-w-0 shrink-[9999] truncate text-muted-foreground">
|
||||
<span dir="auto" className="min-w-0 shrink-[9999] truncate text-muted-foreground">
|
||||
{mvRow && '\u00a0-\u00a0'}
|
||||
{previewSnippet ? <> <SearchSnippetText snippet={previewSnippet} /></> : (mvRow ? trimmedPreview : inlinePreview)}
|
||||
</span>
|
||||
@@ -1492,7 +1492,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
<span className="sr-only">{threadAccountDescription}</span>
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
"truncate text-sm",
|
||||
hasUnread
|
||||
? "font-bold text-foreground"
|
||||
@@ -1572,7 +1572,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
<span dir="auto" className={cn(
|
||||
"min-w-0 flex-1 truncate text-sm",
|
||||
hasUnread
|
||||
? "font-semibold text-foreground"
|
||||
@@ -1585,7 +1585,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
|
||||
{showPreview && density !== 'extra-compact' && density !== 'compact' && (
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p className={cn(
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : "line-clamp-2",
|
||||
hasUnread
|
||||
|
||||
Reference in New Issue
Block a user