175 lines
4.1 KiB
TypeScript
175 lines
4.1 KiB
TypeScript
// ==================== PDF SERVICE ====================
|
|
|
|
import PDFDocument from 'pdfkit';
|
|
|
|
interface EmailData {
|
|
from: string;
|
|
to: string;
|
|
cc?: string;
|
|
subject: string;
|
|
date: Date;
|
|
bodyText?: string;
|
|
bodyHtml?: string;
|
|
}
|
|
|
|
/**
|
|
* Generiert ein PDF aus einer E-Mail
|
|
*/
|
|
export async function generateEmailPdf(email: EmailData): Promise<Buffer> {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
const doc = new PDFDocument({
|
|
size: 'A4',
|
|
margins: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
});
|
|
|
|
const chunks: Buffer[] = [];
|
|
|
|
doc.on('data', (chunk) => chunks.push(chunk));
|
|
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
doc.on('error', reject);
|
|
|
|
// Header
|
|
doc
|
|
.fontSize(18)
|
|
.font('Helvetica-Bold')
|
|
.text('E-Mail', { align: 'center' });
|
|
|
|
doc.moveDown(1.5);
|
|
|
|
// Metadaten-Tabelle
|
|
doc.fontSize(10).font('Helvetica');
|
|
|
|
// Von
|
|
doc
|
|
.font('Helvetica-Bold')
|
|
.text('Von: ', { continued: true })
|
|
.font('Helvetica')
|
|
.text(email.from);
|
|
|
|
// An
|
|
doc
|
|
.font('Helvetica-Bold')
|
|
.text('An: ', { continued: true })
|
|
.font('Helvetica')
|
|
.text(email.to);
|
|
|
|
// CC (falls vorhanden)
|
|
if (email.cc) {
|
|
doc
|
|
.font('Helvetica-Bold')
|
|
.text('CC: ', { continued: true })
|
|
.font('Helvetica')
|
|
.text(email.cc);
|
|
}
|
|
|
|
// Datum
|
|
const formattedDate = email.date.toLocaleString('de-DE', {
|
|
dateStyle: 'full',
|
|
timeStyle: 'short',
|
|
});
|
|
doc
|
|
.font('Helvetica-Bold')
|
|
.text('Datum: ', { continued: true })
|
|
.font('Helvetica')
|
|
.text(formattedDate);
|
|
|
|
// Betreff
|
|
doc
|
|
.font('Helvetica-Bold')
|
|
.text('Betreff: ', { continued: true })
|
|
.font('Helvetica')
|
|
.text(email.subject || '(Kein Betreff)');
|
|
|
|
doc.moveDown(1);
|
|
|
|
// Trennlinie
|
|
doc
|
|
.moveTo(50, doc.y)
|
|
.lineTo(doc.page.width - 50, doc.y)
|
|
.stroke();
|
|
|
|
doc.moveDown(1);
|
|
|
|
// Inhalt
|
|
doc.fontSize(11);
|
|
|
|
// HTML in Text konvertieren (vereinfacht)
|
|
let content = email.bodyText || '';
|
|
|
|
if (!content && email.bodyHtml) {
|
|
// Einfache HTML-zu-Text Konvertierung
|
|
content = htmlToPlainText(email.bodyHtml);
|
|
}
|
|
|
|
if (!content) {
|
|
content = '(Kein Inhalt)';
|
|
}
|
|
|
|
// Text mit Zeilenumbrüchen ausgeben
|
|
doc.text(content, {
|
|
align: 'left',
|
|
lineGap: 2,
|
|
});
|
|
|
|
// Footer mit Erstellungsdatum
|
|
const footerY = doc.page.height - 40;
|
|
doc
|
|
.fontSize(8)
|
|
.fillColor('#666666')
|
|
.text(
|
|
`Exportiert am ${new Date().toLocaleString('de-DE')}`,
|
|
50,
|
|
footerY,
|
|
{ align: 'center', width: doc.page.width - 100 }
|
|
);
|
|
|
|
doc.end();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Konvertiert HTML in Plain-Text (vereinfacht)
|
|
*/
|
|
function htmlToPlainText(html: string): string {
|
|
let text = html;
|
|
|
|
// Zeilenumbrüche vor Block-Elementen
|
|
text = text.replace(/<(br|p|div|h[1-6]|li|tr)[^>]*>/gi, '\n');
|
|
|
|
// Listen-Elemente
|
|
text = text.replace(/<li[^>]*>/gi, '\n• ');
|
|
|
|
// Links mit URL
|
|
text = text.replace(/<a[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, '$2 ($1)');
|
|
|
|
// Alle anderen Tags entfernen
|
|
text = text.replace(/<[^>]+>/g, '');
|
|
|
|
// HTML-Entities dekodieren
|
|
text = text.replace(/ /g, ' ');
|
|
text = text.replace(/&/g, '&');
|
|
text = text.replace(/</g, '<');
|
|
text = text.replace(/>/g, '>');
|
|
text = text.replace(/"/g, '"');
|
|
text = text.replace(/'/g, "'");
|
|
text = text.replace(/ä/g, 'ä');
|
|
text = text.replace(/ö/g, 'ö');
|
|
text = text.replace(/ü/g, 'ü');
|
|
text = text.replace(/Ä/g, 'Ä');
|
|
text = text.replace(/Ö/g, 'Ö');
|
|
text = text.replace(/Ü/g, 'Ü');
|
|
text = text.replace(/ß/g, 'ß');
|
|
|
|
// Mehrfache Leerzeilen reduzieren
|
|
text = text.replace(/\n{3,}/g, '\n\n');
|
|
|
|
// Führende/folgende Leerzeichen entfernen
|
|
text = text.trim();
|
|
|
|
return text;
|
|
}
|