import { useEffect, useMemo, useState } from 'react' import { save } from '@tauri-apps/plugin-dialog' import { CalendarCheck, CalendarClock, CalendarPlus, CalendarX, CheckSquare, Download, ListPlus, Mail, Paperclip, } from 'lucide-react' import type { AttachmentMeta, InviteInfo, InviteResponse, MessageFull } from '../types' import { Avatar } from './Avatar' import { api } from '../lib/api' import { formatBytes, formatFullDate } from '../lib/format' import { AttachmentPreviewModal, isPreviewable } from './AttachmentPreviewModal' function isIcsAttachment(att: AttachmentMeta) { return att.mime_type.toLowerCase() === 'text/calendar' || att.filename.toLowerCase().endsWith('.ics') } function InviteBanner({ info, onRespond, }: { info: InviteInfo onRespond: (response: InviteResponse) => Promise }) { const [responding, setResponding] = useState(null) const [responded, setResponded] = useState(null) async function respond(r: InviteResponse) { setResponding(r) try { await onRespond(r) setResponded(r) } finally { setResponding(null) } } const dateLabel = `${new Date(info.start_ts * 1000).toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short' })}${ info.all_day ? '' : `, ${new Date(info.start_ts * 1000).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} – ${new Date(info.end_ts * 1000).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}` }` return (

Termineinladung: {info.summary}

{dateLabel} {info.organizer_email && ` · von ${info.organizer_name || info.organizer_email}`}

{responded ? (

{responded === 'accepted' && 'Angenommen — Antwort wurde gesendet.'} {responded === 'declined' && 'Abgelehnt — Antwort wurde gesendet.'} {responded === 'tentative' && 'Mit Vorbehalt beantwortet — Antwort wurde gesendet.'}

) : (
)}
) } function downloadAttachment(filename: string, mimeType: string, base64: string) { const byteChars = atob(base64) const bytes = new Uint8Array(byteChars.length) for (let i = 0; i < byteChars.length; i++) bytes[i] = byteChars.charCodeAt(i) const blob = new Blob([bytes], { type: mimeType || 'application/octet-stream' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename a.click() URL.revokeObjectURL(url) } export function ReadingPane({ message, loading, selectionCount = 0, onImportIcsAttachment, onCreateTask, onReplyToInvite, }: { message: MessageFull | null loading: boolean selectionCount?: number onImportIcsAttachment?: (base64: string) => Promise onCreateTask?: (message: MessageFull) => void onReplyToInvite?: (accountId: string, icsBase64: string, response: InviteResponse) => Promise }) { const [importingIcs, setImportingIcs] = useState(null) const [previewAttachment, setPreviewAttachment] = useState(null) const [invite, setInvite] = useState<{ base64: string; info: InviteInfo } | null>(null) useEffect(() => { setInvite(null) if (!message || !onReplyToInvite) return const icsAtt = message.attachments.find(isIcsAttachment) if (!icsAtt) return let cancelled = false api .previewInvite(icsAtt.content_base64) .then((info) => { if (!cancelled && info) setInvite({ base64: icsAtt.content_base64, info }) }) .catch(() => {}) return () => { cancelled = true } }, [message, onReplyToInvite]) const srcDoc = useMemo(() => { if (!message) return '' if (message.body_html) { return `${message.body_html}` } const escaped = (message.body_text || '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' })[c]!) return `${escaped}` }, [message]) async function handleExportEml(m: MessageFull) { const suggested = `${m.subject.replace(/[\\/:*?"<>|]/g, '_').slice(0, 60) || 'nachricht'}.eml` const path = await save({ defaultPath: suggested, filters: [{ name: 'E-Mail', extensions: ['eml'] }] }) if (!path) return await api.exportMessageEml(m.account_id, m.folder, m.uid, path) } if (loading) { return (
Lade Nachricht…
) } if (selectionCount > 1) { return (

{selectionCount} Nachrichten ausgewählt

) } if (!message) { return (

Wähle eine Nachricht aus

) } return (

{message.subject || '(kein Betreff)'}

{message.from_name || message.from_addr} <{message.from_addr}>

an {message.to || 'mich'} · {formatFullDate(message.date)}

{onCreateTask && ( )}
{invite && onReplyToInvite && ( onReplyToInvite(message.account_id, invite.base64, response)} /> )} {message.attachments.length > 0 && (
{message.attachments.map((att, i) => (
{isIcsAttachment(att) && onImportIcsAttachment && ( )}
))}
)}