fix: show calendar, subscription and account addresses in settings left to right in arabic

This commit is contained in:
Linus Rath
2026-10-28 12:58:10 +01:00
parent b957bbcd8a
commit f441f3e6d1
8 changed files with 46 additions and 12 deletions
@@ -68,9 +68,22 @@ describe('CalendarManagementSettings CalDAV fallback address', () => {
render(<CalendarManagementSettings />);
expect(await screen.findByText('https://host.example/stalwart/.well-known/caldav')).toBeInTheDocument();
const url = await screen.findByText('https://host.example/stalwart/.well-known/caldav');
// Left to right in Arabic too: the start of the address stays in view.
expect(url).toHaveAttribute('dir', 'ltr');
vi.restoreAllMocks();
});
it('shows a subscription address left to right', () => {
useAuthStore.setState({ client: { getAccountId: () => 'acct-1' }, serverUrl: null, username: null } as never);
useCalendarStore.setState({
calendars: [appleColored],
icalSubscriptions: [{ id: 's', name: 'Holidays', url: 'https://example.com/holidays.ics', color: '#ff0000', calendarId: 'x' }],
updateCalendar: vi.fn(),
} as never);
render(<CalendarManagementSettings />);
expect(screen.getByText('https://example.com/holidays.ics')).toHaveAttribute('dir', 'ltr');
});
});
// Phone: seven icons in a row hid the calendar names. The row actions are
@@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';
import { useSettingsStore } from '@/stores/settings-store';
import { SidebarAppsSettings } from '../sidebar-apps-settings';
// Addresses read left to right in an Arabic layout too; in a right-to-left
// box a cut address lost its start ("https://") instead of its end.
const initial = useSettingsStore.getState();
afterEach(() => { useSettingsStore.setState(initial, true); });
describe('addresses in Settings', () => {
it('shows a sidebar app address left to right', () => {
useSettingsStore.setState({
sidebarApps: [{ id: 'a', name: 'Wiki', url: 'https://wiki.example.com/start', icon: 'globe', openMode: 'inline' }],
} as never);
render(<SidebarAppsSettings />);
expect(screen.getByText('https://wiki.example.com/start')).toHaveAttribute('dir', 'ltr');
});
});
@@ -334,7 +334,7 @@ function TotpSection() {
</div>
)}
<div className="flex items-center gap-2">
<code className="text-xs bg-background px-2 py-1 rounded border border-border flex-1 truncate">{setupUrl}</code>
<code dir="ltr" className="text-xs bg-background px-2 py-1 rounded border border-border flex-1 truncate">{setupUrl}</code>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">{t('password.current')}</label>
+3 -3
View File
@@ -155,13 +155,13 @@ export function AccountSettings() {
{/* Email Address */}
<SettingItem label={t('email.label')}>
<span className="text-sm text-foreground">{email ? toUnicodeEmail(email) : tCommon('unknown')}</span>
<span dir={email ? 'ltr' : undefined} className="text-sm text-foreground">{email ? toUnicodeEmail(email) : tCommon('unknown')}</span>
</SettingItem>
{/* Username / Login (show when it differs from email) */}
{username && username !== email && (
<SettingItem label={t('username_label')}>
<span className="text-sm text-foreground">{toUnicodeEmail(username)}</span>
<span dir="ltr" className="text-sm text-foreground">{toUnicodeEmail(username)}</span>
</SettingItem>
)}
@@ -410,7 +410,7 @@ function AccountRow({
<Star className="w-3 h-3 text-amber-500 flex-shrink-0 fill-amber-500" aria-label={labels.default} />
)}
</div>
<p className="text-xs text-muted-foreground truncate">
<p dir="ltr" className="text-xs text-muted-foreground truncate rtl:text-right">
{toUnicodeEmail(account.email || account.username)}
</p>
<div className="flex items-center gap-1 mt-0.5">
@@ -727,7 +727,8 @@ export function CalendarManagementSettings() {
return (
<div className="flex items-center gap-1 mt-0.5">
<Link className="w-3 h-3 text-muted-foreground flex-shrink-0" />
<span className="text-xs text-muted-foreground truncate" title={caldavUrl}>
{/* An address reads left to right in Arabic too, cut at its end. */}
<span dir="ltr" className="text-xs text-muted-foreground truncate" title={caldavUrl}>
{caldavUrl}
</span>
<IconButton
@@ -881,7 +882,7 @@ export function CalendarManagementSettings() {
{(() => {
const safeUrl = redactUrlCredentials(sub.url);
return (
<span className="text-xs text-muted-foreground truncate block" title={safeUrl}>
<span dir="ltr" className="text-xs text-muted-foreground truncate block rtl:text-right" title={safeUrl}>
{safeUrl}
</span>
);
+2 -2
View File
@@ -125,7 +125,7 @@ export function MultiAccountPushSettings({ relayBaseUrl }: { relayBaseUrl?: stri
<span>
<span className="block">{account.displayName || account.label || account.username}</span>
<span className="block text-xs text-muted-foreground">
{account.email || account.username} · {enabled[account.id] ? t('state_on') : t('state_off')}
<bdi dir="ltr">{account.email || account.username}</bdi> · {enabled[account.id] ? t('state_on') : t('state_off')}
</span>
</span>
</label>
@@ -143,7 +143,7 @@ export function MultiAccountPushSettings({ relayBaseUrl }: { relayBaseUrl?: stri
{failures.length > 0 && <ul role="alert" className="space-y-1 text-sm text-destructive">
{failures.map(f => <li key={f.id}>
<span className="font-medium">{f.name}</span>
{f.email && f.email !== f.name && <span> ({f.email})</span>}
{f.email && f.email !== f.name && <span> (<bdi dir="ltr">{f.email}</bdi>)</span>}
<span>: {f.reason}</span>
</li>)}
</ul>}
@@ -401,7 +401,7 @@ export function ShareCollectionDialog({
)}
</div>
{p.email && p.email !== p.name && (
<div className="text-xs text-muted-foreground truncate">{p.email}</div>
<div dir="ltr" className="text-xs text-muted-foreground truncate rtl:text-right">{p.email}</div>
)}
</div>
{savingId === p.id && <Loader2 className="w-4 h-4 animate-spin" />}
@@ -206,7 +206,7 @@ function ManagedAppsSection() {
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{app.name}</div>
<div className="text-xs text-muted-foreground truncate">{app.url}</div>
<div dir="ltr" className="text-xs text-muted-foreground truncate rtl:text-right">{app.url}</div>
</div>
<span className="text-[10px] px-1.5 py-0.5 rounded-full flex-shrink-0 bg-muted text-muted-foreground inline-flex items-center gap-1">
<Lock className="w-2.5 h-2.5" />
@@ -332,7 +332,7 @@ export function SidebarAppsSettings() {
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{app.name}</div>
<div className="text-xs text-muted-foreground truncate">{app.url}</div>
<div dir="ltr" className="text-xs text-muted-foreground truncate rtl:text-right">{app.url}</div>
</div>
<span className={cn(
"text-[10px] px-1.5 py-0.5 rounded-full flex-shrink-0",