mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: show calendar, subscription and account addresses in settings left to right in arabic
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user