From 351938aa5c0033e4d95f95b229a605de139ff089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Sun, 10 May 2026 21:32:19 +0200 Subject: [PATCH] feat: add email recipient history to user settings and implement management in InboxDetailPage --- .../database/entities/user-settings.entity.ts | 3 ++ .../user-settings/user-settings.service.ts | 4 ++ paperless-frontend/src/api/userSettings.ts | 1 + .../src/pages/InboxDetailPage.tsx | 51 +++++++++++++++++-- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/paperless-backend/src/database/entities/user-settings.entity.ts b/paperless-backend/src/database/entities/user-settings.entity.ts index 3259e3f..2fec5b4 100644 --- a/paperless-backend/src/database/entities/user-settings.entity.ts +++ b/paperless-backend/src/database/entities/user-settings.entity.ts @@ -31,4 +31,7 @@ export class UserSettings { @Column({ type: 'int', nullable: true }) DefaultLabelTemplateId!: number | null; + + @Column({ type: 'json', nullable: true }) + EmailRecipientHistory!: string[] | null; } diff --git a/paperless-backend/src/user-settings/user-settings.service.ts b/paperless-backend/src/user-settings/user-settings.service.ts index b271e80..b11c08c 100644 --- a/paperless-backend/src/user-settings/user-settings.service.ts +++ b/paperless-backend/src/user-settings/user-settings.service.ts @@ -18,6 +18,7 @@ export interface UserSettingsDto { smtpFromName: string | null; mailSignatureHtml: string | null; defaultLabelTemplateId: number | null; + emailRecipientHistory: string[] | null; } @Injectable() @@ -80,6 +81,7 @@ export class UserSettingsService { smtpFromName?: string | null; mailSignatureHtml?: string | null; defaultLabelTemplateId?: number | null; + emailRecipientHistory?: string[] | null; }, ): Promise { let entity = await this.repo.findOne({ where: { UserId: userId } }); @@ -98,6 +100,7 @@ export class UserSettingsService { if (data.smtpFromName !== undefined) entity.SmtpFromName = data.smtpFromName; if (data.mailSignatureHtml !== undefined) entity.MailSignatureHtml = data.mailSignatureHtml; if (data.defaultLabelTemplateId !== undefined) entity.DefaultLabelTemplateId = data.defaultLabelTemplateId; + if (data.emailRecipientHistory !== undefined) entity.EmailRecipientHistory = data.emailRecipientHistory; await this.repo.save(entity); return this.toDto(entity); @@ -168,6 +171,7 @@ export class UserSettingsService { smtpFromName: entity?.SmtpFromName ?? null, mailSignatureHtml: entity?.MailSignatureHtml ?? null, defaultLabelTemplateId: entity?.DefaultLabelTemplateId ?? null, + emailRecipientHistory: entity?.EmailRecipientHistory ?? null, }; } } diff --git a/paperless-frontend/src/api/userSettings.ts b/paperless-frontend/src/api/userSettings.ts index 6b39dae..a0a919a 100644 --- a/paperless-frontend/src/api/userSettings.ts +++ b/paperless-frontend/src/api/userSettings.ts @@ -10,6 +10,7 @@ export interface UserSettingsData { smtpFromName: string | null; mailSignatureHtml: string | null; defaultLabelTemplateId: number | null; + emailRecipientHistory: string[] | null; } export interface SenderOption { diff --git a/paperless-frontend/src/pages/InboxDetailPage.tsx b/paperless-frontend/src/pages/InboxDetailPage.tsx index e21669c..0d50c8d 100644 --- a/paperless-frontend/src/pages/InboxDetailPage.tsx +++ b/paperless-frontend/src/pages/InboxDetailPage.tsx @@ -659,6 +659,7 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose const [filenames, setFilenames] = useState([]); const [senders, setSenders] = useState([]); const [selectedSender, setSelectedSender] = useState('default'); + const [recipientHistory, setRecipientHistory] = useState([]); const editorRef = useRef(null); useEffect(() => { @@ -672,6 +673,7 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose ); userSettingsApi.get().then((settings) => { editorRef.current?.setContent(settings.mailSignatureHtml ?? ''); + setRecipientHistory(settings.emailRecipientHistory ?? []); }).catch(() => {}); userSettingsApi.getSenders().then((s) => { setSenders(s); @@ -679,12 +681,21 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose }).catch(() => setSenders([])); }, [open, documents, fileName, form]); + const removeRecipient = (addr: string, e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const updated = recipientHistory.filter((a) => a !== addr); + setRecipientHistory(updated); + userSettingsApi.update({ emailRecipientHistory: updated }).catch(() => {}); + }; + const handleOk = async () => { try { const values = await form.validateFields(); setSubmitting(true); + const addresses: string[] = values.to ?? []; await inboxApi.sendEmail(fileId, { - to: values.to, + to: addresses.join(', '), subject: values.subject, body: editorRef.current?.getText() ?? '', html: editorRef.current?.getHTML(), @@ -694,6 +705,12 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose })), sender: senders.length > 1 ? selectedSender : undefined, }); + const updated = [ + ...addresses, + ...recipientHistory.filter((a) => !addresses.includes(a)), + ].slice(0, 20); + setRecipientHistory(updated); + userSettingsApi.update({ emailRecipientHistory: updated }).catch(() => {}); message.success('E-Mail wurde gesendet'); onClose(); } catch (err: any) { @@ -726,8 +743,36 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose /> )} - - + { + if (!value?.length) return Promise.reject('Bitte mindestens einen Empfänger angeben'); + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const bad = value.find((v) => !re.test(v)); + return bad ? Promise.reject(`Ungültige Adresse: ${bad}`) : Promise.resolve(); + }, + }]} + > +