- {effectiveAttachments.slice(0, 2).map((attachment) => {
- const { icon: FileIcon, className: fileIconClass } = attachmentIcon(attachment.type, attachment.name);
- const isPreviewable = isAttachmentPreviewable(attachment);
- const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
- const thumbUrl = imageThumbUrls[attachment.id];
- return (
-
- {(dragProps) => (<>
- handleEffectiveAttachmentOpen(attachment)}
- data-testid="attachment"
- data-attachment-name={attachment.name}
- draggable={dragProps.draggable}
- onPointerEnter={dragProps.onPointerEnter}
- onDragStart={dragProps.onDragStart}
- onDragEnd={dragProps.onDragEnd}
- >
- {thumbUrl && (
-
-
-
- )}
-
-
-
- {getAttachmentDisplayName(attachment.name, attachment.type)}
-
-
- {formatFileSize(attachment.size)}
-
-
-
-
{ e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); }}
- >
-
-
- {opensPreview && (
-
{ e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); }}
- >
-
-
- )}
- {canSaveToFiles && attachment.blobId && (
-
{ e.stopPropagation(); setSaveToFilesTarget(attachment); }}>
-
-
- )}
-
-
-
-
- >)}
-
- );
- })}
- {effectiveAttachments.length > 2 && (
-
setShowAllBesideAttachments(!showAllBesideAttachments)}
- className="text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-0.5"
- >
- +{effectiveAttachments.length - 2} {t('more')}
-
- )}
- {downloadAllButton}
- {/* Floating popup for remaining attachments */}
- {showAllBesideAttachments && effectiveAttachments.length > 2 && (
- <>
-
setShowAllBesideAttachments(false)} />
-
- {effectiveAttachments.slice(2).map((attachment) => {
- const { icon: FileIcon, className: fileIconClass } = attachmentIcon(attachment.type, attachment.name);
- const isPreviewable = isAttachmentPreviewable(attachment);
- const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
- return (
-
- {(dragProps) => (<>
- { handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
- draggable={dragProps.draggable}
- onPointerEnter={dragProps.onPointerEnter}
- onDragStart={dragProps.onDragStart}
- onDragEnd={dragProps.onDragEnd}
- >
-
-
- {getAttachmentDisplayName(attachment.name, attachment.type)}
-
-
- {formatFileSize(attachment.size)}
-
-
-
{ e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); setShowAllBesideAttachments(false); }}
- >
-
-
- {opensPreview && (
-
{ e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
- >
-
-
- )}
- {canSaveToFiles && attachment.blobId && (
-
{ e.stopPropagation(); setSaveToFilesTarget(attachment); }}>
-
-
- )}
-
-
-
-
- >)}
-
- );
- })}
-
- >
- )}
-
- )}
@@ -5906,11 +5738,10 @@ export function EmailViewer({
+
)}
diff --git a/components/settings/reading-settings.tsx b/components/settings/reading-settings.tsx
index 39cbd2790..7b782ec74 100644
--- a/components/settings/reading-settings.tsx
+++ b/components/settings/reading-settings.tsx
@@ -162,10 +162,9 @@ export function ReadingSettings() {
updateSetting('attachmentPosition', value as AttachmentPosition)}
options={[
- { value: 'beside-sender', label: t('attachment_position.beside-sender') },
{ value: 'below-header', label: t('attachment_position.below-header') },
{ value: 'below-message', label: t('attachment_position.below-message') },
]}
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 66bc1b72f..992faf0ac 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "موضع المرفق",
"description": "أين تُعرض المرفقات في ترويسة الرسالة",
- "beside-sender": "بجانب المرسل",
"below-header": "أسفل الترويسة"
},
"emails_per_page": {
diff --git a/locales/ca/common.json b/locales/ca/common.json
index ea59b952d..f5687f2bc 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Posició dels fitxers adjunts",
"description": "On mostrar els fitxers adjunts a la capçalera del correu",
- "beside-sender": "Al costat del remitent",
"below-header": "Sota la capçalera"
},
"emails_per_page": {
diff --git a/locales/cs/common.json b/locales/cs/common.json
index ebf45f5ab..4165fcdcd 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Pozice příloh",
"description": "Kde zobrazovat přílohy v hlavičce e-mailu",
- "beside-sender": "Vedle odesílatele",
"below-header": "Pod hlavičkou"
},
"emails_per_page": {
diff --git a/locales/da/common.json b/locales/da/common.json
index 1edf0549c..4ffdcc90c 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Placering af vedhæftninger",
"description": "Hvor vedhæftninger skal vises i e-mail-headeren",
- "beside-sender": "Ved siden af afsender",
"below-header": "Under header"
},
"emails_per_page": {
diff --git a/locales/de/common.json b/locales/de/common.json
index 4905539a0..683230c84 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -1903,8 +1903,7 @@
"attachment_position": {
"label": "Anhangsposition",
"description": "Wo die Anhänge einer geöffneten E-Mail angezeigt werden",
- "beside-sender": "Neben dem Absender",
- "below-header": "Unter dem Header",
+ "below-header": "Als Leiste unter dem Header",
"below-message": "Als Karten unter der Nachricht"
},
"emails_per_page": {
diff --git a/locales/en/common.json b/locales/en/common.json
index 6f747623b..e5a3bc1ea 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -1906,8 +1906,7 @@
"attachment_position": {
"label": "Attachment position",
"description": "Where to display the attachments of an open email",
- "beside-sender": "Next to sender",
- "below-header": "Below header",
+ "below-header": "Bar below the header",
"below-message": "Cards below the message"
},
"emails_per_page": {
diff --git a/locales/es/common.json b/locales/es/common.json
index e94157cb3..fb1937046 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Posición del adjunto",
"description": "Dónde mostrar los adjuntos en el encabezado del correo",
- "beside-sender": "Junto al remitente",
"below-header": "Debajo del encabezado"
},
"emails_per_page": {
diff --git a/locales/et/common.json b/locales/et/common.json
index 7e9d987e2..a48fd665c 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Manuse asukoht",
"description": "Kuhu kuvada manused kirja päises",
- "beside-sender": "Saatja kõrval",
"below-header": "Päise all"
},
"emails_per_page": {
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 78cd59e77..9dba3ad7c 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "موقعیت پیوست",
"description": "محل پیوستها",
- "beside-sender": "کنار فرستنده",
"below-header": "زیر هدر"
},
"emails_per_page": {
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 0bdacbe58..80d5fe208 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Position des pièces jointes",
"description": "Où afficher les pièces jointes dans l'en-tête de l'email",
- "beside-sender": "À côté de l'expéditeur",
"below-header": "Sous l'en-tête"
},
"emails_per_page": {
diff --git a/locales/he/common.json b/locales/he/common.json
index c0f6c0c1d..289536d1d 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -1837,7 +1837,6 @@
"attachment_position": {
"label": "עמדת התקשרות",
"description": "היכן להציג קבצים מצורפים בכותרת הדוא\"ל",
- "beside-sender": "ליד השולח",
"below-header": "מתחת לכותרת"
},
"emails_per_page": {
diff --git a/locales/hu/common.json b/locales/hu/common.json
index cdbbca20c..313a7030c 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Csatolmány pozíciója",
"description": "Hol jelenjenek meg a csatolmányok az e-mail fejlécben",
- "beside-sender": "Feladó mellett",
"below-header": "Fejléc alatt"
},
"emails_per_page": {
diff --git a/locales/it/common.json b/locales/it/common.json
index 48ff32bf4..96f9aa5d8 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Posizione degli allegati",
"description": "Dove visualizzare gli allegati nell'intestazione dell'email",
- "beside-sender": "Accanto al mittente",
"below-header": "Sotto l'intestazione"
},
"emails_per_page": {
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 0cc87579d..5cc6cb0d4 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "添付ファイルの位置",
"description": "メールヘッダー内での添付ファイルの表示位置",
- "beside-sender": "送信者の横",
"below-header": "ヘッダーの下"
},
"emails_per_page": {
diff --git a/locales/ko/common.json b/locales/ko/common.json
index c9b21b2c4..4d9a2d749 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "첨부파일 위치",
"description": "메일 상단에서 첨부파일을 어디에 표시할지 설정해요",
- "beside-sender": "보낸 사람 옆",
"below-header": "제목 아래"
},
"emails_per_page": {
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 9084df8d8..d2f605921 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Pielikumu novietojums",
"description": "Kur rādīt pielikumus vēstules galvenē",
- "beside-sender": "Blakus sūtītājam",
"below-header": "Zem galvenes"
},
"emails_per_page": {
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 2940d16e3..670da454e 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Хавсралтын байрлал",
"description": "Имэйл толгой хэсэгт хавсралтуудыг хаана харуулах вэ",
- "beside-sender": "Илгээгчийн хажууд",
"below-header": "Толгойн доор"
},
"emails_per_page": {
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 4c8a42a98..09f80d3c9 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Plassering av vedlegg",
"description": "Velg hvor vedlegg skal vises i e-postoverskriften",
- "beside-sender": "Ved siden av avsender",
"below-header": "Under overskriften"
},
"emails_per_page": {
diff --git a/locales/nl/common.json b/locales/nl/common.json
index 9579031e8..fdd207b5e 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Positie van bijlagen",
"description": "Waar bijlagen in de e-mailkop worden weergegeven",
- "beside-sender": "Naast de afzender",
"below-header": "Onder de kop"
},
"emails_per_page": {
diff --git a/locales/pl/common.json b/locales/pl/common.json
index 0ba0c8530..86b6174c3 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Pozycja załączników",
"description": "Gdzie wyświetlać załączniki w nagłówku wiadomości e-mail",
- "beside-sender": "Obok nadawcy",
"below-header": "Pod nagłówkiem"
},
"emails_per_page": {
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 56b2f7dc9..5895060d2 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Posição dos anexos",
"description": "Onde exibir os anexos no cabeçalho do e-mail",
- "beside-sender": "Ao lado do remetente",
"below-header": "Abaixo do cabeçalho"
},
"emails_per_page": {
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 3e23426c4..8fabd2d36 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Poziția atașamentului",
"description": "Unde să afișați atașamentele în antetul e-mailului",
- "beside-sender": "Lângă expeditor",
"below-header": "Sub antet"
},
"emails_per_page": {
diff --git a/locales/ru/common.json b/locales/ru/common.json
index 932db4221..00491dff4 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Расположение вложений",
"description": "Где отображать вложения в заголовке письма",
- "beside-sender": "Рядом с отправителем",
"below-header": "Под заголовком"
},
"emails_per_page": {
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 6d1c4fefa..fcd171908 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "Pozícia príloh",
"description": "Kde zobrazovať prílohy v hlavičke e-mailu",
- "beside-sender": "Vedľa odosielateľa",
"below-header": "Pod hlavičkou"
},
"emails_per_page": {
diff --git a/locales/th/common.json b/locales/th/common.json
index 5144d2da8..6a7659ec8 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -4344,7 +4344,6 @@
"attachment_position": {
"label": "ตำแหน่งไฟล์แนบ",
"description": "ตำแหน่งที่จะแสดงไฟล์แนบในส่วนหัวของอีเมล",
- "beside-sender": "ข้างผู้ส่ง",
"below-header": "ใต้ส่วนหัว"
},
"emails_per_page": {
diff --git a/locales/tr/common.json b/locales/tr/common.json
index 981df0421..ed1e61e2c 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Ek Konumu",
"description": "E-posta başlığında eklerin nerede görüntüleneceği",
- "beside-sender": "Göndericinin yanı",
"below-header": "Başlığın altı"
},
"emails_per_page": {
diff --git a/locales/uk/common.json b/locales/uk/common.json
index 5e597d1fa..c18e4e299 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "Позиція прикріплення",
"description": "Де відображати вкладення в заголовку електронної пошти",
- "beside-sender": "Поруч із відправником",
"below-header": "Під заголовком"
},
"emails_per_page": {
diff --git a/locales/vi/common.json b/locales/vi/common.json
index 16e55ab2c..c577a5e38 100644
--- a/locales/vi/common.json
+++ b/locales/vi/common.json
@@ -1915,7 +1915,6 @@
"attachment_position": {
"label": "Vị trí tệp đính kèm",
"description": "Vị trí hiển thị tệp đính kèm trong phần đầu thư",
- "beside-sender": "Bên cạnh người gửi",
"below-header": "Bên dưới phần đầu thư"
},
"emails_per_page": {
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index 0133522f0..a2c0219a1 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -1903,7 +1903,6 @@
"attachment_position": {
"label": "附件位置",
"description": "選擇在郵件標頭的哪個位置顯示附件",
- "beside-sender": "寄件者旁",
"below-header": "標頭下方"
},
"emails_per_page": {
diff --git a/locales/zh/common.json b/locales/zh/common.json
index c5f7b4182..da45e9c9f 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -1900,7 +1900,6 @@
"attachment_position": {
"label": "附件位置",
"description": "在邮件标题中的何处显示附件",
- "beside-sender": "发件人旁",
"below-header": "标题下方"
},
"emails_per_page": {
diff --git a/stores/__tests__/settings-store-attachments.test.ts b/stores/__tests__/settings-store-attachments.test.ts
index 4c9b658ca..9f682f056 100644
--- a/stores/__tests__/settings-store-attachments.test.ts
+++ b/stores/__tests__/settings-store-attachments.test.ts
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it } from 'vitest';
-import { useSettingsStore } from '../settings-store';
+import { migrateSettings, useSettingsStore } from '../settings-store';
describe('settings-store attachment action', () => {
beforeEach(() => {
@@ -14,6 +14,28 @@ describe('settings-store attachment action', () => {
expect(useSettingsStore.getState().quickReplyEnabled).toBe(true);
});
+ it('shows attachments as a bar under the header by default', () => {
+ expect(useSettingsStore.getState().attachmentPosition).toBe('below-header');
+ });
+
+ it('moves the retired beside-sender position to the bar', () => {
+ const migrated = migrateSettings({ attachmentPosition: 'beside-sender', explicitSettings: [] }, 11) as unknown as Record;
+ expect(migrated.attachmentPosition).toBe('below-header');
+ const cards = migrateSettings({ attachmentPosition: 'below-message', explicitSettings: [] }, 11) as unknown as Record;
+ expect(cards.attachmentPosition).toBe('below-message');
+ });
+
+ it('does not count the old default as chosen when older settings migrate', () => {
+ const migrated = migrateSettings({ attachmentPosition: 'beside-sender' }, 7) as unknown as Record;
+ expect(migrated.explicitSettings).not.toContain('attachmentPosition');
+ });
+
+ it('moves beside-sender to the bar in settings synced from an older build', () => {
+ useSettingsStore.getState().updateSetting('attachmentPosition', 'below-message');
+ expect(useSettingsStore.getState().importSettings(JSON.stringify({ attachmentPosition: 'beside-sender' }))).toBe(true);
+ expect(useSettingsStore.getState().attachmentPosition).toBe('below-header');
+ });
+
it('defaults to preview when settings are reset', () => {
expect(useSettingsStore.getState().mailAttachmentAction).toBe('preview');
});
diff --git a/stores/settings-store.ts b/stores/settings-store.ts
index 211685e6a..bea2dfa98 100644
--- a/stores/settings-store.ts
+++ b/stores/settings-store.ts
@@ -302,8 +302,8 @@ export type FirstDayOfWeek = 0 | 1 | 6; // 0 = Sunday, 1 = Monday, 6 = Saturday
export type TimeZoneSetting = 'auto' | (string & {});
export type ExternalContentPolicy = 'ask' | 'block' | 'allow';
export type MailAttachmentAction = 'preview' | 'download';
-/** 'below-message' shows larger preview cards after the body. */
-export type AttachmentPosition = 'beside-sender' | 'below-header' | 'below-message';
+/** 'below-header': a bar of tiles under the header. 'below-message': preview cards after the body. */
+export type AttachmentPosition = 'below-header' | 'below-message';
export type ToolbarPosition = 'top' | 'below-subject';
export type ArchiveMode = 'single' | 'year' | 'month';
export type MailLayout = 'split' | 'focus' | 'horizontal';
@@ -901,7 +901,7 @@ const DEFAULT_SETTINGS = {
messageSpacing: 'auto' as MessageSpacing,
plainTextFont: 'sans' as PlainTextFont,
mailAttachmentAction: 'preview' as MailAttachmentAction,
- attachmentPosition: 'beside-sender' as AttachmentPosition,
+ attachmentPosition: 'below-header' as AttachmentPosition,
emailAlwaysLightMode: false,
archiveMode: 'single' as ArchiveMode,
hoverActions: ['delete', 'star', 'markRead', 'archive'] as HoverAction[],
@@ -1547,6 +1547,7 @@ export const useSettingsStore = create()(
migrateAnimationsSetting(settings);
migrateNotificationSoundDefault(settings);
migrateTintListRowsDefault(settings);
+ migrateAttachmentPosition(settings);
// Apply settings, noting which keys were taken over for the
// onSettingsImport hook.
@@ -1948,7 +1949,7 @@ export const useSettingsStore = create()(
},
{
name: 'settings-storage',
- version: 11,
+ version: 12,
migrate: migrateSettings,
onRehydrateStorage: () => {
return (state) => {
@@ -2053,6 +2054,8 @@ export function migrateSettings(persisted: unknown, version: number): SettingsSt
// taken as chosen below.
migrateNotificationSoundDefault(state);
migrateTintListRowsDefault(state);
+ // The old default must not be taken as chosen below either.
+ migrateAttachmentPosition(state);
// v8: admin policy defaults (#1248) skip settings the user chose.
// Earlier builds did not record that, so a setting that differs from
// the build default is taken as chosen.
@@ -2063,6 +2066,18 @@ export function migrateSettings(persisted: unknown, version: number): SettingsSt
return state as unknown as SettingsState;
}
+/**
+ * v12: the attachments next to the sender ('beside-sender', the old default)
+ * gave way to the bar under the header. Imports and server copies from older
+ * builds still carry the old value and go through this too.
+ */
+export function migrateAttachmentPosition(state: Record): void {
+ if (state.attachmentPosition === undefined) return;
+ if (state.attachmentPosition !== 'below-header' && state.attachmentPosition !== 'below-message') {
+ state.attachmentPosition = 'below-header';
+ }
+}
+
/**
* v9: the Animations on/off switch became Reduce motion (System / On /
* Off). Off meant "no animations"; On let the OS setting decide, which is