feat(i18n): Hebrew locale + full RTL support

This commit is contained in:
Shuki Vaknin
2026-07-08 00:07:34 +02:00
committed by Linus Rath
parent 8904d724bb
commit 22418c17cf
113 changed files with 3958 additions and 494 deletions
@@ -1053,7 +1053,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
setShowCalendarPicker(false);
handleImport(cal.id);
}}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2"
>
<span
className="w-3 h-3 rounded-full flex-shrink-0"
@@ -1095,7 +1095,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
)}
{isProcessing && (
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground ml-auto" />
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground ms-auto" />
)}
</div>
)}
+11 -11
View File
@@ -1990,7 +1990,7 @@ export function EmailComposer({
<div ref={composerRootRef} className={cn("flex h-full bg-background", className)}>
<PluginSlot
name="composer-sidebar"
className="hidden md:flex shrink-0 h-full overflow-hidden border-r border-border"
className="hidden md:flex shrink-0 h-full overflow-hidden border-e border-border"
/>
{/* Right-side composer sidebar slot is rendered after the main content div below. */}
<div
@@ -2047,7 +2047,7 @@ export function EmailComposer({
size="sm"
className="md:hidden h-9 px-4"
>
<Send className="w-4 h-4 mr-1.5" />
<Send className="w-4 h-4 me-1.5" />
{t('send')}
</Button>
</div>
@@ -2445,7 +2445,7 @@ export function EmailComposer({
)}
<button
onClick={() => removeAttachment(index)}
className="ml-1 hover:text-red-500 min-w-[20px] min-h-[20px] flex items-center justify-center"
className="ms-1 hover:text-red-500 min-w-[20px] min-h-[20px] flex items-center justify-center"
title={att.uploading ? t('upload_cancel') : undefined}
>
<X className="w-3 h-3" />
@@ -2540,9 +2540,9 @@ export function EmailComposer({
onClick={() => handleSend()}
disabled={!canSend || isSending}
title={getSendTooltip()}
className="rounded-r-none border-r border-primary-foreground/20"
className="rounded-e-none border-e border-primary-foreground/20"
>
<Send className="w-4 h-4 mr-2" />
<Send className="w-4 h-4 me-2" />
{t('send')}
</Button>
<Button
@@ -2550,7 +2550,7 @@ export function EmailComposer({
onClick={() => setShowSendMenu((open) => !open)}
disabled={!canSend || isSending}
title={t('schedule_send')}
className="rounded-l-none px-2"
className="rounded-s-none px-2"
aria-haspopup="menu"
aria-expanded={showSendMenu}
>
@@ -2565,7 +2565,7 @@ export function EmailComposer({
type="button"
role="menuitem"
onClick={openScheduleDialog}
className="flex w-full items-center gap-2 rounded-sm px-3 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground"
className="flex w-full items-center gap-2 rounded-sm px-3 py-2 text-start text-sm hover:bg-accent hover:text-accent-foreground"
>
<CalendarClock className="w-4 h-4" />
{t('schedule_send')}
@@ -2580,7 +2580,7 @@ export function EmailComposer({
title={getSendTooltip()}
className="hidden md:inline-flex"
>
<Send className="w-4 h-4 mr-2" />
<Send className="w-4 h-4 me-2" />
{t('send')}
</Button>
)}
@@ -2699,7 +2699,7 @@ export function EmailComposer({
{t('discard')}
</Button>
<Button onClick={handleSaveDraftAndClose}>
<Save className="w-4 h-4 mr-2" />
<Save className="w-4 h-4 me-2" />
{t('save_draft')}
</Button>
</div>
@@ -2718,7 +2718,7 @@ export function EmailComposer({
</div>
<PluginSlot
name="composer-sidebar-right"
className="hidden md:flex shrink-0 h-full overflow-hidden border-l border-border"
className="hidden md:flex shrink-0 h-full overflow-hidden border-s border-border"
/>
</div>
);
@@ -2743,7 +2743,7 @@ const AutocompleteDropdown = React.forwardRef<HTMLDivElement, {
role="option"
aria-selected={i === selectedIndex}
className={cn(
"w-full px-3 py-2 text-left text-sm flex items-center gap-2",
"w-full px-3 py-2 text-start text-sm flex items-center gap-2",
i === selectedIndex ? "bg-accent text-accent-foreground" : "hover:bg-muted"
)}
onMouseDown={(e) => {
+2 -2
View File
@@ -334,7 +334,7 @@ export function EmailContextMenu({
</div>
)}
{node.children.length > 0 && (
<div className="pl-4">
<div className="ps-4">
{renderNodes(node.children)}
</div>
)}
@@ -377,7 +377,7 @@ export function EmailContextMenu({
role="menuitem"
onClick={() => handleAction(() => onSetColorTag?.(option.value))}
className={cn(
"w-full px-3 py-1.5 text-sm text-left flex items-center gap-2 hover:bg-muted cursor-pointer",
"w-full px-3 py-1.5 text-sm text-start flex items-center gap-2 hover:bg-muted cursor-pointer",
isActive && "bg-accent font-medium"
)}
>
+8 -7
View File
@@ -181,16 +181,17 @@ export function EmailHoverActions({
return (
<div
className="absolute right-0 top-0 bottom-0 z-10 hidden group-hover:flex items-center"
className="absolute end-0 top-0 bottom-0 z-10 hidden group-hover:flex items-center"
>
<div
className={cn("w-8 h-full", hoverBackgroundClassName)}
style={{
WebkitMaskImage: "linear-gradient(to right, transparent, black)",
maskImage: "linear-gradient(to right, transparent, black)",
}}
className={cn(
"w-8 h-full",
"[mask-image:linear-gradient(to_right,transparent,black)] [-webkit-mask-image:linear-gradient(to_right,transparent,black)]",
"rtl:[mask-image:linear-gradient(to_left,transparent,black)] rtl:[-webkit-mask-image:linear-gradient(to_left,transparent,black)]",
hoverBackgroundClassName,
)}
/>
<div className={cn("flex items-center gap-0.5 h-full pr-3 pl-0.5", hoverBackgroundClassName)}>
<div className={cn("flex items-center gap-0.5 h-full pe-3 ps-0.5", hoverBackgroundClassName)}>
{actionButtons}
</div>
</div>
+1 -1
View File
@@ -176,7 +176,7 @@ export function EmailListItem({ email, selected, onClick, onDoubleClick, onConte
{/* Unread indicator */}
{isUnread && (
<div className="absolute left-0.5 top-1/2 -translate-y-1/2">
<div className="absolute start-0.5 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
+2 -2
View File
@@ -417,9 +417,9 @@ export function EmailList({
className="text-destructive border-destructive/30 hover:bg-destructive/10 text-xs"
>
{isProcessing ? (
<Loader2 className="w-3 h-3 animate-spin mr-1" />
<Loader2 className="w-3 h-3 animate-spin me-1" />
) : (
<Trash2 className="w-3 h-3 mr-1" />
<Trash2 className="w-3 h-3 me-1" />
)}
{t('empty_folder.button')}
</Button>
+65 -65
View File
@@ -333,7 +333,7 @@ function renderClickableRecipients(
return (
<span key={r.email + index} className="inline-flex items-center">
{index > 0 && <span className="text-muted-foreground mr-1">,</span>}
{index > 0 && <span className="text-muted-foreground me-1">,</span>}
<RecipientPopover
name={r.name}
email={r.email}
@@ -382,7 +382,7 @@ export function ContactSidebarPanel({
};
return (
<div className="w-[320px] shrink-0 border-l border-border bg-background flex flex-col h-full animate-in slide-in-from-right-5 duration-200">
<div className="w-[320px] shrink-0 border-s border-border bg-background flex flex-col h-full animate-in slide-in-from-right-5 duration-200">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
<h3 className="text-sm font-semibold text-foreground truncate">{t('contact_sidebar.title')}</h3>
@@ -607,7 +607,7 @@ function SidebarSection({ icon: Icon, title, children }: { icon: React.Component
<Icon className="w-3.5 h-3.5 text-muted-foreground" />
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">{title}</h4>
</div>
<div className="space-y-1 pl-5.5">{children}</div>
<div className="space-y-1 ps-5.5">{children}</div>
</div>
);
}
@@ -2637,7 +2637,7 @@ export function EmailViewer({
<h3 className="text-xl font-semibold text-foreground mb-3">{tDemoWelcome('title')}</h3>
<p className="text-sm text-muted-foreground mb-6 leading-relaxed">{tDemoWelcome('description')}</p>
<div className="flex flex-col gap-3 items-center">
<div className="grid grid-cols-2 gap-3 text-left text-sm text-muted-foreground w-full">
<div className="grid grid-cols-2 gap-3 text-start text-sm text-muted-foreground w-full">
<div className="flex items-center gap-2">
<Mail className="w-4 h-4 text-primary shrink-0" />
<span>{tDemoWelcome('feature_email')}</span>
@@ -2679,7 +2679,7 @@ export function EmailViewer({
<p className="text-muted-foreground">{t('no_conversation_description')}</p>
{onCompose && (
<Button onClick={onCompose} className="mt-6" title={t('compose_hint')}>
<PenSquare className="w-4 h-4 mr-2" />
<PenSquare className="w-4 h-4 me-2" />
{t('compose')}
</Button>
)}
@@ -2704,7 +2704,7 @@ export function EmailViewer({
variant="ghost"
size="icon"
onClick={onBack}
className="h-9 w-9 flex-shrink-0 -ml-1"
className="h-9 w-9 flex-shrink-0 -ms-1"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
@@ -2839,7 +2839,7 @@ export function EmailViewer({
{showToolbarLabels && <span className="text-[10px] leading-tight sm:text-sm">{t('move')}</span>}
</Button>
{moveMenuOpen && (
<div className="absolute right-0 top-full mt-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-lg shadow-lg border border-border z-10">
<div className="absolute end-0 top-full mt-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-lg shadow-lg border border-border z-10">
{(() => {
const renderNodes = (nodes: MailboxNode[], depth = 0) => {
return nodes.map((node) => {
@@ -2850,7 +2850,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoveMenuOpen(false); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2"
style={{ paddingLeft: `${0.75 + depth * 1}rem` }}
>
<Icon className="w-4 h-4 flex-shrink-0" />
@@ -2910,7 +2910,7 @@ export function EmailViewer({
)}
</button>
{tagMenuOpen && (
<div className="absolute right-0 top-full mt-1 py-1 w-40 bg-background rounded-lg shadow-lg border border-border z-10">
<div className="absolute end-0 top-full mt-1 py-1 w-40 bg-background rounded-lg shadow-lg border border-border z-10">
{colorOptions.map((option) => {
const isActive = currentColors.includes(option.value);
return (
@@ -2918,13 +2918,13 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setTagMenuOpen(false); }}
className={cn(
"w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2",
"w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3 h-3 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-3 h-3 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-3 h-3 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
@@ -2933,7 +2933,7 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setTagMenuOpen(false); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2 text-muted-foreground"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2 text-muted-foreground"
>
<X className="w-3 h-3 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3037,11 +3037,11 @@ export function EmailViewer({
<span className="text-[10px] leading-tight sm:hidden">{t('more_actions')}</span>
</Button>
{moreMenuOpen && !isMobile && (
<div className="absolute right-0 top-full mt-1 w-48 bg-background rounded-md shadow-lg border border-border z-10 py-1">
<div className="absolute end-0 top-full mt-1 w-48 bg-background rounded-md shadow-lg border border-border z-10 py-1">
{/* Star toggle */}
<button
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Star className={cn("w-4 h-4", isStarred && "fill-yellow-400 text-yellow-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
@@ -3049,7 +3049,7 @@ export function EmailViewer({
{/* Overflow: reply */}
<button
onClick={() => { onReply?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(1) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(1) ? "" : "sm:hidden")}
>
<Reply className="w-4 h-4" />
{t('reply')}
@@ -3057,7 +3057,7 @@ export function EmailViewer({
{/* Overflow: reply all */}
<button
onClick={() => { onReplyAll?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(2) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(2) ? "" : "sm:hidden")}
>
<ReplyAll className="w-4 h-4" />
{t('reply_all')}
@@ -3065,7 +3065,7 @@ export function EmailViewer({
{/* Overflow: forward */}
<button
onClick={() => { onForward?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(3) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(3) ? "" : "sm:hidden")}
>
<Forward className="w-4 h-4" />
{t('forward')}
@@ -3073,7 +3073,7 @@ export function EmailViewer({
{/* Overflow: archive */}
<button
onClick={() => { onArchive?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(4) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(4) ? "" : "sm:hidden")}
>
<Archive className="w-4 h-4" />
{t('archive')}
@@ -3086,14 +3086,14 @@ export function EmailViewer({
>
<button
onClick={() => setMoreMenuSub(moreMenuSub === 'move' ? null : 'move')}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<FolderInput className="w-4 h-4" />
<span className="flex-1">{t('move_to')}</span>
<ChevronRight className="w-3 h-3 text-muted-foreground" />
</button>
{moreMenuSub === 'move' && (
<div className="absolute right-full top-0 mr-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-md shadow-lg border border-border z-10">
<div className="absolute end-full top-0 me-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-md shadow-lg border border-border z-10">
{(() => {
const renderMobileNodes = (nodes: MailboxNode[], depth = 0) => {
return nodes.map((node) => {
@@ -3104,7 +3104,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2"
style={{ paddingLeft: `${0.75 + depth * 1}rem` }}
>
<Icon className="w-4 h-4 flex-shrink-0" />
@@ -3138,14 +3138,14 @@ export function EmailViewer({
>
<button
onClick={() => setMoreMenuSub(moreMenuSub === 'tag' ? null : 'tag')}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Tag className="w-4 h-4" />
<span className="flex-1">{t('tag')}</span>
<ChevronRight className="w-3 h-3 text-muted-foreground" />
</button>
{moreMenuSub === 'tag' && (
<div className="absolute right-full top-0 mr-1 py-1 w-40 bg-background rounded-md shadow-lg border border-border z-10">
<div className="absolute end-full top-0 me-1 py-1 w-40 bg-background rounded-md shadow-lg border border-border z-10">
{colorOptions.map((option) => {
const isActive = currentColors.includes(option.value);
return (
@@ -3153,13 +3153,13 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn(
"w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2",
"w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3 h-3 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-3 h-3 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-3 h-3 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
@@ -3168,7 +3168,7 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2 text-muted-foreground"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2 text-muted-foreground"
>
<X className="w-3 h-3 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3183,7 +3183,7 @@ export function EmailViewer({
{spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<button
onClick={() => { (isInJunkFolder ? onUndoSpam : onMarkAsSpam)?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
>
{isInJunkFolder ? (
<ShieldCheck className="h-4 w-4 text-green-600 dark:text-green-400" />
@@ -3196,7 +3196,7 @@ export function EmailViewer({
{/* Overflow: toggle read */}
<button
onClick={() => { onMarkAsRead?.(email.id, isUnread); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(8) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(8) ? "" : "sm:hidden")}
>
{isUnread ? <MailOpen className="w-4 h-4" /> : <Mail className="w-4 h-4" />}
{isUnread ? t('mark_read') : t('mark_unread')}
@@ -3204,7 +3204,7 @@ export function EmailViewer({
{/* Overflow: print */}
<button
onClick={() => { handlePrint(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(9) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(9) ? "" : "sm:hidden")}
>
<Printer className="w-4 h-4" />
{t('print')}
@@ -3212,7 +3212,7 @@ export function EmailViewer({
{/* Overflow: view source */}
<button
onClick={() => { setShowSourceModal(true); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(10) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(10) ? "" : "sm:hidden")}
>
<Code className="w-4 h-4" />
{t('view_source')}
@@ -3221,7 +3221,7 @@ export function EmailViewer({
{effectiveEmailContent.isHtml && (
<button
onClick={() => { setEmailViewDarkOverride(prev => prev === null ? !(resolvedTheme === 'dark') : !prev); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(11) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(11) ? "" : "sm:hidden")}
>
{isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
{isDark ? 'View in light mode' : 'View in dark mode'}
@@ -3231,7 +3231,7 @@ export function EmailViewer({
{/* Export email */}
<button
onClick={() => { handleExportEmail(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Download className="w-4 h-4" />
{t('export_email')}
@@ -3239,7 +3239,7 @@ export function EmailViewer({
{/* Import email */}
<button
onClick={() => { handleImportEmail(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Upload className="w-4 h-4" />
{t('import_email')}
@@ -3247,7 +3247,7 @@ export function EmailViewer({
{onShowShortcuts && (
<button
onClick={() => { onShowShortcuts(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Keyboard className="w-4 h-4" />
{t('keyboard_shortcuts')}
@@ -3275,7 +3275,7 @@ export function EmailViewer({
)}
{!isScheduled && isMobile && (
<div className={cn(
"fixed inset-y-0 right-0 w-72 bg-background border-l border-border z-[70] sm:hidden",
"fixed inset-y-0 right-0 w-72 bg-background border-s border-border z-[70] sm:hidden",
"transform transition-transform duration-300 ease-in-out",
"flex flex-col",
moreMenuOpen ? "translate-x-0" : "translate-x-full"
@@ -3284,7 +3284,7 @@ export function EmailViewer({
{moreMenuSub ? (
<button
onClick={() => setMoreMenuSub(null)}
className="flex items-center gap-1 -ml-2 px-2 py-1 rounded hover:bg-muted text-sm font-semibold text-foreground"
className="flex items-center gap-1 -ms-2 px-2 py-1 rounded hover:bg-muted text-sm font-semibold text-foreground"
>
<ChevronLeft className="w-5 h-5" />
{moreMenuSub === 'move' ? t('move_to') : t('tag')}
@@ -3302,7 +3302,7 @@ export function EmailViewer({
{/* Star toggle */}
<button
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Star className={cn("w-5 h-5", isStarred && "fill-yellow-400 text-yellow-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
@@ -3311,12 +3311,12 @@ export function EmailViewer({
{colorOptions.length > 0 && (
<button
onClick={() => setMoreMenuSub('tag')}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Tag className="w-5 h-5" />
<span className="flex-1">{t('tag')}</span>
{currentColors.length > 0 && (
<div className="flex -space-x-1 mr-1">
<div className="flex -space-x-1 me-1">
{currentColors.slice(0, 3).map((c) => {
const opt = colorOptions.find((o) => o.value === c);
return opt ? <span key={c} className={cn("w-3 h-3 rounded-full border border-background", opt.color)} /> : null;
@@ -3328,14 +3328,14 @@ export function EmailViewer({
)}
<button
onClick={() => { handlePrint(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Printer className="w-5 h-5" />
{t('print')}
</button>
<button
onClick={() => { setShowSourceModal(true); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Code className="w-5 h-5" />
{t('view_source')}
@@ -3343,7 +3343,7 @@ export function EmailViewer({
{effectiveEmailContent.isHtml && (
<button
onClick={() => { setEmailViewDarkOverride(prev => prev === null ? !(resolvedTheme === 'dark') : !prev); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
{isDark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
{isDark ? 'View in light mode' : 'View in dark mode'}
@@ -3352,14 +3352,14 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { handleExportEmail(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Download className="w-5 h-5" />
{t('export_email')}
</button>
<button
onClick={() => { handleImportEmail(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Upload className="w-5 h-5" />
{t('import_email')}
@@ -3367,7 +3367,7 @@ export function EmailViewer({
{onShowShortcuts && (
<button
onClick={() => { onShowShortcuts(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Keyboard className="w-5 h-5" />
{t('keyboard_shortcuts')}
@@ -3385,7 +3385,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3"
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3"
style={{ paddingLeft: `${1 + depth * 1}rem` }}
>
<Icon className="w-5 h-5 flex-shrink-0" />
@@ -3416,20 +3416,20 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn(
"w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3",
"w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3.5 h-3.5 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-4 h-4 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-4 h-4 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
{currentColors.length > 0 && (
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3 text-muted-foreground"
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3 text-muted-foreground"
>
<X className="w-4 h-4 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3469,7 +3469,7 @@ export function EmailViewer({
variant="ghost"
size="icon"
onClick={onBack}
className="h-11 w-11 lg:h-10 lg:w-10 flex-shrink-0 -ml-2"
className="h-11 w-11 lg:h-10 lg:w-10 flex-shrink-0 -ms-2"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
@@ -3513,7 +3513,7 @@ export function EmailViewer({
</div>
</div>
{/* Date/time on the right of subject row - hidden on mobile, shown next to sender */}
<div className="hidden sm:block flex-shrink-0 text-right">
<div className="hidden sm:block flex-shrink-0 text-end">
<span className="text-xs lg:text-sm text-muted-foreground whitespace-nowrap">
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
</span>
@@ -3569,7 +3569,7 @@ export function EmailViewer({
name={sender?.name}
email={sender.email}
onViewContact={handleViewContactSidebar}
className="font-semibold text-left"
className="font-semibold text-start"
/>
) : (
<span className="font-semibold text-foreground">{t('unknown_sender')}</span>
@@ -3633,7 +3633,7 @@ export function EmailViewer({
)}
<button
onClick={() => setShowFullHeaders(!showFullHeaders)}
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ml-1"
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ms-1"
>
{showFullHeaders ? (
<>
@@ -3756,7 +3756,7 @@ export function EmailViewer({
<span className="text-xs text-foreground truncate max-w-[180px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
<span className="text-[10px] text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -3814,7 +3814,7 @@ export function EmailViewer({
name={sender?.name}
email={sender.email}
onViewContact={handleViewContactSidebar}
className="text-sm font-semibold text-left"
className="text-sm font-semibold text-start"
/>
) : (
<span className="text-sm font-semibold text-foreground">{t('unknown_sender')}</span>
@@ -3857,7 +3857,7 @@ export function EmailViewer({
)}
<button
onClick={() => setShowFullHeaders(!showFullHeaders)}
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ml-1"
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ms-1"
>
{showFullHeaders ? (
<>
@@ -3874,7 +3874,7 @@ export function EmailViewer({
</div>
</div>
{/* Date/time + size on the right (mobile) */}
<div className="sm:hidden flex-shrink-0 text-right ml-2">
<div className="sm:hidden flex-shrink-0 text-end ms-2">
<span className="text-xs text-muted-foreground whitespace-nowrap">
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
</span>
@@ -4006,7 +4006,7 @@ export function EmailViewer({
email={sender?.email || ''}
displayLabel={sender?.name && sender?.email ? `${sender.name} <${sender.email}>` : undefined}
onViewContact={handleViewContactSidebar}
className="text-sm text-left"
className="text-sm text-start"
/>
</div>
</Row>
@@ -4534,7 +4534,7 @@ export function EmailViewer({
<span className="text-sm text-foreground truncate max-w-[220px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-xs text-muted-foreground ml-auto flex-shrink-0">
<span className="text-xs text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -4672,7 +4672,7 @@ export function EmailViewer({
<span className="text-xs text-foreground truncate max-w-[180px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
<span className="text-[10px] text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -4815,7 +4815,7 @@ export function EmailViewer({
disabled={isSendingQuickReply}
className="text-muted-foreground"
>
<MoreVertical className="w-4 h-4 mr-1" />
<MoreVertical className="w-4 h-4 me-1" />
{t('more_options')}
</Button>
<Button
@@ -4825,12 +4825,12 @@ export function EmailViewer({
>
{isSendingQuickReply ? (
<>
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
<Loader2 className="w-4 h-4 me-1 animate-spin" />
{t('sending')}
</>
) : (
<>
<Reply className="w-4 h-4 mr-1" />
<Reply className="w-4 h-4 me-1" />
{t('send')}
</>
)}
@@ -4966,7 +4966,7 @@ export function EmailViewer({
<>
{/* Collapse toggle when sidebar is collapsed */}
{detailSidebarCollapsed && (
<div className="flex flex-col items-center border-l border-border bg-background">
<div className="flex flex-col items-center border-s border-border bg-background">
<Button
variant="ghost"
size="icon"
@@ -4990,7 +4990,7 @@ export function EmailViewer({
onDoubleClick={() => setDetailSidebarWidth(280)}
/>
<div
className="flex flex-col h-full border-l border-border bg-background overflow-hidden"
className="flex flex-col h-full border-s border-border bg-background overflow-hidden"
style={{ width: detailSidebarWidth, minWidth: detailSidebarWidth }}
>
<div className="flex items-center justify-end px-1 py-1 border-b border-border shrink-0">
+1 -1
View File
@@ -31,7 +31,7 @@ export function ReadReceiptBanner({ requestedBy, onSend, onIgnore }: ReadReceipt
<MailCheck className="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" />
<span className="text-foreground">{t('prompt')}</span>
<span className="break-all text-muted-foreground">{requestedBy}</span>
<div className="ml-auto flex items-center gap-2">
<div className="ms-auto flex items-center gap-2">
<button
onClick={async () => {
setState('sending');
+1 -1
View File
@@ -221,7 +221,7 @@ export function RecipientPopover({ name, email, displayLabel, onViewContact, cla
{onViewContact && (
<button
onClick={handleViewContact}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground px-2 py-1.5 rounded hover:bg-muted transition-colors ml-auto"
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground px-2 py-1.5 rounded hover:bg-muted transition-colors ms-auto"
title={contact ? "View contact" : "View details"}
>
{contact ? <ExternalLink className="w-3.5 h-3.5" /> : <UserPlus className="w-3.5 h-3.5" />}
+29 -8
View File
@@ -8,6 +8,7 @@ import Heading from "@tiptap/extension-heading";
import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import TextAlign from "@tiptap/extension-text-align";
import { TextDirection } from "@/components/email/text-direction";
import { TextStyle } from "@tiptap/extension-text-style";
import Color from "@tiptap/extension-color";
import { ResizableImage } from "@/components/email/resizable-image";
@@ -19,6 +20,7 @@ import { TableCell } from "@tiptap/extension-table-cell";
import { QuotedHtml, serializeEditorContent } from "@/components/email/quoted-html";
import { SignatureBlock } from "@/components/email/signature-block";
import { cn } from "@/lib/utils";
import { useSettingsStore } from "@/stores/settings-store";
import {
Bold,
Italic,
@@ -29,6 +31,7 @@ import {
AlignLeft,
AlignCenter,
AlignRight,
ArrowLeftRight,
Link as LinkIcon,
Undo,
Redo,
@@ -183,6 +186,7 @@ export function RichTextEditor({
hasError,
onEditorReady,
}: RichTextEditorProps) {
const rtlEditingSupport = useSettingsStore((st) => st.rtlEditingSupport);
const onImageUploadRef = React.useRef(onImageUpload);
onImageUploadRef.current = onImageUpload;
const onEditorReadyRef = React.useRef(onEditorReady);
@@ -240,6 +244,7 @@ export function RichTextEditor({
// rich/branded signatures keep their inline styling in the editor and
// in the sent mail (see signature-block.ts).
SignatureBlock,
TextDirection,
],
content,
editorProps: {
@@ -454,6 +459,22 @@ export function RichTextEditor({
<AlignRight className="w-4 h-4" />
</ToolbarButton>
{rtlEditingSupport && (
<ToolbarButton
active={
(editor.getAttributes("paragraph").dir || editor.getAttributes("heading").dir) === "rtl"
}
onClick={() => {
const cur =
editor.getAttributes("paragraph").dir || editor.getAttributes("heading").dir;
editor.chain().focus().setTextDirection(cur === "rtl" ? "ltr" : "rtl").run();
}}
title="Text direction (RTL/LTR)"
>
<ArrowLeftRight className="w-4 h-4" />
</ToolbarButton>
)}
<ToolbarSeparator />
<ToolbarButton
@@ -478,28 +499,28 @@ export function RichTextEditor({
<div className="flex flex-col gap-0.5">
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().addRowBefore().run(); setTableMenuOpen(false); }}
>
<Rows3 className="w-4 h-4" /> Add row above
</button>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().addRowAfter().run(); setTableMenuOpen(false); }}
>
<Rows3 className="w-4 h-4" /> Add row below
</button>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().addColumnBefore().run(); setTableMenuOpen(false); }}
>
<Columns3 className="w-4 h-4" /> Add column before
</button>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().addColumnAfter().run(); setTableMenuOpen(false); }}
>
<Columns3 className="w-4 h-4" /> Add column after
@@ -507,21 +528,21 @@ export function RichTextEditor({
<div className="h-px bg-border my-1" />
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().deleteRow().run(); setTableMenuOpen(false); }}
>
<Trash2 className="w-4 h-4" /> Delete row
</button>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().deleteColumn().run(); setTableMenuOpen(false); }}
>
<Trash2 className="w-4 h-4" /> Delete column
</button>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start"
onClick={() => { editor.chain().focus().toggleHeaderRow().run(); setTableMenuOpen(false); }}
>
<Rows3 className="w-4 h-4" /> Toggle header row
@@ -529,7 +550,7 @@ export function RichTextEditor({
<div className="h-px bg-border my-1" />
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-left text-red-600 dark:text-red-400"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent text-start text-red-600 dark:text-red-400"
onClick={() => { editor.chain().focus().deleteTable().run(); setTableMenuOpen(false); }}
>
<Trash2 className="w-4 h-4" /> Delete table
+58
View File
@@ -0,0 +1,58 @@
import { Extension } from "@tiptap/core";
export type TextDir = "ltr" | "rtl";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
textDirection: {
setTextDirection: (dir: TextDir) => ReturnType;
unsetTextDirection: () => ReturnType;
};
}
}
/**
* Adds a `dir` attribute to block nodes so the composer can mark individual
* paragraphs/headings as LTR or RTL (Gmail-style right-to-left editing). The
* attribute round-trips to HTML so the direction is preserved in the sent mail.
*/
export const TextDirection = Extension.create({
name: "textDirection",
addOptions() {
return { types: ["paragraph", "heading", "blockquote", "listItem"] };
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
dir: {
default: null,
parseHTML: (element) => element.getAttribute("dir") || null,
renderHTML: (attributes) =>
attributes.dir ? { dir: attributes.dir } : {},
},
},
},
];
},
addCommands() {
return {
setTextDirection:
(dir) =>
({ commands }) =>
this.options.types.every((type: string) =>
commands.updateAttributes(type, { dir }),
),
unsetTextDirection:
() =>
({ commands }) =>
this.options.types.every((type: string) =>
commands.resetAttributes(type, "dir"),
),
};
},
});
@@ -150,7 +150,7 @@ export function ThreadConversationView({
<div className="flex items-center px-4 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 sticky top-0 z-10" style={{ gap: 'var(--density-item-gap)', paddingBlock: 'var(--density-header-py)' }}>
<button
onClick={onBack}
className="p-2 -ml-2 rounded-full hover:bg-muted transition-colors"
className="p-2 -ms-2 rounded-full hover:bg-muted transition-colors"
>
<ArrowLeft className="w-5 h-5" />
</button>
@@ -488,13 +488,13 @@ function EmailCard({
<div className={cn(
"rounded-lg border border-border overflow-hidden transition-all duration-200",
isExpanded ? "bg-background shadow-sm" : "bg-muted/30",
isUnread && !isExpanded && "border-l-2 border-l-primary"
isUnread && !isExpanded && "border-s-2 border-l-primary"
)}>
{/* Card Header - Always visible */}
<button
onClick={onToggleExpanded}
className={cn(
"w-full flex items-start text-left transition-colors",
"w-full flex items-start text-start transition-colors",
!isExpanded && "hover:bg-muted/50"
)}
style={{ gap: 'var(--density-item-gap)', padding: 'var(--density-card-p)' }}
@@ -657,7 +657,7 @@ function EmailCard({
}}
className="flex-1"
>
<Reply className="w-4 h-4 mr-2" />
<Reply className="w-4 h-4 me-2" />
{t("email_viewer.reply")}
</Button>
)}
@@ -671,7 +671,7 @@ function EmailCard({
}}
className="flex-1"
>
<ReplyAll className="w-4 h-4 mr-2" />
<ReplyAll className="w-4 h-4 me-2" />
{t("email_viewer.reply_all")}
</Button>
)}
@@ -685,7 +685,7 @@ function EmailCard({
}}
className="flex-1"
>
<Forward className="w-4 h-4 mr-2" />
<Forward className="w-4 h-4 me-2" />
{t("email_viewer.forward")}
</Button>
)}
+3 -3
View File
@@ -86,8 +86,8 @@ export function ThreadEmailItem({
{...longPressHandlers}
className={cn(
"relative cursor-pointer select-none transition-all duration-150",
"pl-12 pr-4",
"border-l-2 border-l-transparent",
"ps-12 pe-4",
"border-s-2 border-l-transparent",
selected
? "bg-selection border-l-primary"
: "hover:bg-muted/50",
@@ -130,7 +130,7 @@ export function ThreadEmailItem({
{/* Unread indicator */}
{isUnread && (
<div className="absolute left-7 top-1/2 -translate-y-1/2">
<div className="absolute start-7 top-1/2 -translate-y-1/2">
<Circle className="w-1.5 h-1.5 fill-unread text-unread" />
</div>
)}
+3 -3
View File
@@ -218,7 +218,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
)}
{isUnread && (
<div className="absolute left-0.5 top-1/2 -translate-y-1/2">
<div className="absolute start-0.5 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
@@ -643,7 +643,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
)}
{hasUnread && (
<div className="absolute left-0.5 top-1/2 -translate-y-1/2">
<div className="absolute start-0.5 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
@@ -896,7 +896,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
<div className="bg-muted/20 animate-in slide-in-from-top-2 duration-200">
{isLoading ? (
<div className="py-4 flex items-center justify-center text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin mr-2" />
<Loader2 className="w-4 h-4 animate-spin me-2" />
{t('loading')}
</div>
) : (
+2 -2
View File
@@ -96,7 +96,7 @@ export function UnsubscribeBanner({
if (success) {
return (
<span className="inline-flex items-center gap-1 ml-1">
<span className="inline-flex items-center gap-1 ms-1">
<CheckCircle className="w-3 h-3 text-green-600 dark:text-green-400" />
<span className="text-xs text-green-600 dark:text-green-400">
{t(unsubMethod === 'http'
@@ -110,7 +110,7 @@ export function UnsubscribeBanner({
if (error) {
return (
<span className="inline-flex items-center gap-1 ml-1">
<span className="inline-flex items-center gap-1 ms-1">
<AlertCircle className="w-3 h-3 text-red-500 dark:text-red-400" />
<button
onClick={onDismiss}