feat: implement multi-segment PDF email attachments and add PWA mobile icons
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s

This commit is contained in:
2026-05-06 10:14:16 +02:00
parent 415f8bbcf3
commit 443ab765c9
7 changed files with 66 additions and 31 deletions
+4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Paperless" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

+1 -1
View File
@@ -119,7 +119,7 @@ export const inboxApi = {
subject: string;
body: string;
html?: string;
filename?: string;
segments: { pages: number[]; filename: string }[];
},
) =>
api.post(`/api/inbox/${encodeURIComponent(id)}/send-email`, body).then(() => {}),
@@ -660,13 +660,15 @@ function TiptapToolbar({ editor }: { editor: ReturnType<typeof useEditor> }) {
interface SendEmailDialogProps {
open: boolean;
fileId: string;
defaultFilename: string;
fileName: string;
documents: DocumentSegment[];
onClose: () => void;
}
function SendEmailDialog({ open, fileId, defaultFilename, onClose }: SendEmailDialogProps) {
function SendEmailDialog({ open, fileId, fileName, documents, onClose }: SendEmailDialogProps) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [filenames, setFilenames] = useState<string[]>([]);
const editor = useEditor({
extensions: [StarterKit, Underline],
@@ -674,12 +676,16 @@ function SendEmailDialog({ open, fileId, defaultFilename, onClose }: SendEmailDi
});
useEffect(() => {
if (open) {
form.resetFields();
form.setFieldsValue({ filename: defaultFilename });
editor?.commands.clearContent();
}
}, [open, defaultFilename, form, editor]);
if (!open) return;
form.resetFields();
editor?.commands.clearContent();
const base = fileName.replace(/\.pdf$/i, '');
setFilenames(
documents.map((doc) =>
doc.belegname || (documents.length === 1 ? base : `${base}_${doc.index + 1}`),
),
);
}, [open, documents, fileName, form, editor]);
const handleOk = async () => {
try {
@@ -690,7 +696,10 @@ function SendEmailDialog({ open, fileId, defaultFilename, onClose }: SendEmailDi
subject: values.subject,
body: editor?.getText() ?? '',
html: editor?.getHTML(),
filename: values.filename || undefined,
segments: documents.map((doc, i) => ({
pages: doc.pages,
filename: filenames[i] || fileName,
})),
});
message.success('E-Mail wurde gesendet');
onClose();
@@ -727,8 +736,26 @@ function SendEmailDialog({ open, fileId, defaultFilename, onClose }: SendEmailDi
<EditorContent editor={editor} style={{ minHeight: 120, outline: 'none' }} />
</div>
</Form.Item>
<Form.Item name="filename" label="Dateiname des Anhangs (ohne .pdf)">
<Input placeholder={defaultFilename} />
<Form.Item label="Anhänge">
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{documents.map((doc, i) => {
const first = doc.pages[0];
const last = doc.pages[doc.pages.length - 1];
const range = first === last ? `Seite ${first}` : `Seiten ${first}${last}`;
return (
<div key={doc.index} style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<span style={{ minWidth: 90, color: '#888', fontSize: 12 }}>{range}</span>
<Input
value={filenames[i] ?? ''}
onChange={(e) =>
setFilenames((prev) => prev.map((f, j) => (j === i ? e.target.value : f)))
}
suffix=".pdf"
/>
</div>
);
})}
</div>
</Form.Item>
</Form>
</Modal>
@@ -1560,7 +1587,8 @@ export default function InboxDetailPage() {
<SendEmailDialog
open={emailDialogOpen}
fileId={file.id}
defaultFilename={file.name.replace(/\.pdf$/i, '')}
fileName={file.name}
documents={documents}
onClose={() => setEmailDialogOpen(false)}
/>
</div>