Feature: attachment preview - reliable MIME + inline PDF on desktop and mobile

- MIME: Stalwart's download endpoint often returns application/octet-stream, so
  blob: previews silently downloaded (UUID filename) instead of rendering.
  Resolve the most specific MIME (attachment type -> filename ext -> blob type)
  and re-wrap the blob; also fixes inline preview for images and video.
- Desktop PDF: render via <iframe> (reliable for blob: PDFs) instead of <object>.
- Mobile PDF: no usable inline viewer (Android shows a blank frame / silent
  download; iOS Safari renders only the first page of a PDF in an <iframe>), so
  render with pdf.js (canvas, dynamic-imported so it stays off the desktop
  bundle; iOS-safe canvas cap). Double-tap zoom (fit -> 2x -> 3x -> fit) and
  2-finger pinch zoom (to 4x), both centred on the gesture and pannable via
  native scrolling.
- Route to pdf.js when navigator.pdfViewerEnabled is false (Android) and on iOS
  (incl. iPadOS, which reports true yet shows only the first page in a frame).
- Modal header gains an open-in-new-tab icon (next to download/close); the
  Android/browser Back button closes the preview instead of navigating the page.
- On a pdf.js render failure, offer an open-in-new-tab action as fallback.
This commit is contained in:
dealerweb
2026-05-31 15:58:53 +02:00
committed by Linus Rath
parent ae66f8d89d
commit 0352312f25
21 changed files with 652 additions and 13 deletions
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Klikněte na libovolnou zprávu vlevo pro začátek, nebo využijte průvodce níže."
},
"files": {
"open_in_new_tab": "Otevřít na nové kartě",
"title": "Soubory",
"search_placeholder": "Hledat soubory...",
"empty_state_title": "Žádné soubory",
+1
View File
@@ -2837,6 +2837,7 @@
"hint": "Klik på en e-mail til venstre for at komme i gang, eller tag rundvisningen nedenfor."
},
"files": {
"open_in_new_tab": "Åbn i ny fane",
"title": "Filer",
"search_placeholder": "Søg efter filer...",
"empty_state_title": "Ingen filer endnu",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Klicken Sie links auf eine E-Mail, um loszulegen, oder starten Sie die Tour."
},
"files": {
"open_in_new_tab": "In neuem Tab öffnen",
"title": "Dateien",
"search_placeholder": "Dateien suchen...",
"empty_state_title": "Noch keine Dateien",
+1
View File
@@ -2837,6 +2837,7 @@
"hint": "Click any email on the left to get started, or take the tour below."
},
"files": {
"open_in_new_tab": "Open in new tab",
"title": "Files",
"search_placeholder": "Search files...",
"empty_state_title": "No files yet",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Haz clic en un correo a la izquierda para empezar, o inicia el tour."
},
"files": {
"open_in_new_tab": "Abrir en una pestaña nueva",
"title": "Archivos",
"search_placeholder": "Buscar archivos...",
"empty_state_title": "Aún no hay archivos",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Cliquez sur un e-mail à gauche pour commencer, ou lancez la visite."
},
"files": {
"open_in_new_tab": "Ouvrir dans un nouvel onglet",
"title": "Fichiers",
"search_placeholder": "Rechercher des fichiers...",
"empty_state_title": "Aucun fichier pour le moment",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Clicca su un'email a sinistra per iniziare, oppure fai il tour."
},
"files": {
"open_in_new_tab": "Apri in una nuova scheda",
"title": "File",
"search_placeholder": "Cerca file...",
"empty_state_title": "Nessun file ancora",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "左のメールをクリックして始めるか、ツアーを開始してください。"
},
"files": {
"open_in_new_tab": "新しいタブで開く",
"title": "ファイル",
"search_placeholder": "ファイルを検索...",
"empty_state_title": "ファイルがありません",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "왼쪽에서 메일을 클릭해서 시작하거나, 아래의 둘러보기를 클릭해 보세요."
},
"files": {
"open_in_new_tab": "새 탭에서 열기",
"title": "파일",
"search_placeholder": "파일 검색...",
"empty_state_title": "아직 파일이 없어요",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Noklikšķiniet uz jebkuru vēstuli kreisajā pusē, lai sāktu."
},
"files": {
"open_in_new_tab": "Atvērt jaunā cilnē",
"title": "Faili",
"search_placeholder": "Meklēt failus...",
"empty_state_title": "Failu vēl nav",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Klik links op een e-mail om te beginnen, of start de tour."
},
"files": {
"open_in_new_tab": "Openen in nieuw tabblad",
"title": "Bestanden",
"search_placeholder": "Bestanden zoeken...",
"empty_state_title": "Nog geen bestanden",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Kliknij dowolną wiadomość po lewej, aby zacząć, albo skorzystaj z przewodnika poniżej."
},
"files": {
"open_in_new_tab": "Otwórz w nowej karcie",
"title": "Pliki",
"search_placeholder": "Szukaj plików...",
"empty_state_title": "Brak plików",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Clique num e-mail à esquerda para começar, ou inicie o tour."
},
"files": {
"open_in_new_tab": "Abrir num novo separador",
"title": "Arquivos",
"search_placeholder": "Pesquisar arquivos...",
"empty_state_title": "Ainda não há arquivos",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Нажмите на любое письмо слева, чтобы начать, или пройдите тур ниже."
},
"files": {
"open_in_new_tab": "Открыть в новой вкладке",
"title": "Файлы",
"search_placeholder": "Поиск файлов...",
"empty_state_title": "Файлов пока нет",
+1
View File
@@ -2837,6 +2837,7 @@
"hint": "Başlamak için soldaki herhangi bir e-postaya tıklayın veya aşağıdaki turu yapın."
},
"files": {
"open_in_new_tab": "Yeni sekmede aç",
"title": "Dosyalar",
"search_placeholder": "Dosyalarda ara...",
"empty_state_title": "Henüz dosya yok",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "Натисніть будь-яку електронну пошту ліворуч, щоб розпочати, або перегляньте огляд нижче."
},
"files": {
"open_in_new_tab": "Відкрити в новій вкладці",
"title": "Файли",
"search_placeholder": "Пошук файлів...",
"empty_state_title": "Файлів ще немає",
+1
View File
@@ -2814,6 +2814,7 @@
"hint": "点击左侧的任意邮件即可开始,或参加下方的导览。"
},
"files": {
"open_in_new_tab": "在新标签页中打开",
"title": "文件",
"search_placeholder": "搜索文件...",
"empty_state_title": "还没有文件",