-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
647 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
tmp/ | ||
*.tmp | ||
tmp.* | ||
*.tmp.* | ||
|
||
dist/ | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,74 @@ | ||
export { demo } from './demo'; | ||
import { promises as fs } from 'fs'; | ||
import { join } from 'path'; | ||
import { simpleParser, AddressObject } from 'mailparser'; | ||
import puppeteer from 'puppeteer'; | ||
import handlebars from 'handlebars'; | ||
import prettyBytes from 'pretty-bytes'; | ||
import StreamModule = require('stream'); | ||
import Stream = StreamModule.Stream; | ||
|
||
type Template = 'thunderbird'; | ||
type Language = 'en'; | ||
|
||
// The header and footer templates don't respect the page styles. They need their own styles, otherwise they will be | ||
// tiny, see: https://github.com/puppeteer/puppeteer/issues/1822#issuecomment-530533300 | ||
const headerFooter = (html: string, align = 'left') => | ||
`<div style="font-size: 10px; margin: 10px 20px; width: 100%; text-align: ${align}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${html}</div>`; | ||
const addressesToString = (addr?: AddressObject | AddressObject[]) => | ||
addr instanceof Array ? addr.reduce((acc, cur) => acc + ', ' + cur.text, '') : addr?.text; | ||
|
||
/** | ||
* Generate a PDF from the provided .eml file. | ||
* | ||
* @param eml The email in .eml format to process. Either provide a file path as a string, which will then be read from | ||
* the filesystem, or the content of the .eml file as a `Buffer` or `Stream`. | ||
* @param options.out_path The file path where the generated PDF should be stored (optional). | ||
* @param options.language The language to be used for the labels in the generated PDF (optional, defaults to `en`). | ||
* Currently, only English is supported. | ||
* @param options.template_name The template to use (optional, defaults to `thunderbird`). Currently, only `thunderbird` | ||
* is supported which will generate output similar to Thunderbird’s “Print to PDF” feature. | ||
* | ||
* @returns A `Buffer` containing the generated PDF. | ||
*/ | ||
export default async function mail2pdf( | ||
eml: string | Buffer | Stream, | ||
options?: { out_path?: string; language?: Language; template_name?: Template } | ||
): Promise<Buffer> { | ||
options = { language: 'en', template_name: 'thunderbird', ...options }; | ||
|
||
const mail = await simpleParser(typeof eml === 'string' ? await fs.readFile(eml) : eml); | ||
|
||
const template = handlebars.compile( | ||
(await fs.readFile(join(__dirname, '..', 'templates', options.template_name + '.hbr'))).toString() | ||
); | ||
const html = template({ | ||
subject: mail.subject, | ||
has_html_body: mail.html !== false, | ||
body: mail.html || mail.textAsHtml, | ||
from: mail.from?.text, | ||
date: mail.date?.toISOString(), | ||
to: addressesToString(mail.to), | ||
cc: addressesToString(mail.cc), | ||
bcc: addressesToString(mail.bcc), | ||
priority: mail.priority, | ||
attachments: mail.attachments | ||
.filter((a) => a.contentDisposition === 'attachment') | ||
.map((a) => ({ ...a, filename: a.filename || '<unnamed>', prettySize: prettyBytes(a.size) })), | ||
}); | ||
|
||
const browser = await puppeteer.launch({ headless: true }); | ||
const page = await browser.newPage(); | ||
await page.setContent(html); | ||
const pdf = await page.pdf({ | ||
format: 'a4', | ||
margin: { top: '20mm', bottom: '20mm', right: '20mm', left: '20mm' }, | ||
displayHeaderFooter: true, | ||
headerTemplate: headerFooter('<span class="title"></span>'), | ||
footerTemplate: headerFooter('<span class="pageNumber"></span>/<span class="totalPages"></span>', 'right'), | ||
printBackground: true, | ||
...(options.out_path ? { path: options.out_path } : {}), | ||
}); | ||
await browser.close(); | ||
|
||
return pdf; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<html> | ||
<head> | ||
<title>{{subject}}</title> | ||
<style> | ||
body { | ||
margin: 0; | ||
font-family: system-ui, sans-serif; | ||
font-size: 16px; | ||
line-height: 1.4; | ||
background-color: transparent !important; | ||
} | ||
|
||
* { | ||
/* | ||
Outlook inserts `page: <something>;` on certain elements which causes Chrome to push them onto a separate | ||
page. This rule disables this as the additional pages contain mostly unnecessary whitespace and Outlook | ||
doesn't even respect those rules itself when printing. | ||
*/ | ||
page: unset !important; | ||
} | ||
|
||
a { | ||
color: rgb(11, 108, 218); | ||
} | ||
|
||
#header { | ||
margin-bottom: 25px; | ||
} | ||
|
||
#attachments { | ||
margin: 0 15px 0 10px; | ||
} | ||
|
||
#attachments h2 { | ||
font-weight: normal; | ||
font-size: 9pt; | ||
border-bottom: 1px solid rgb(87, 87, 87); | ||
line-height: 0; | ||
margin: 10px -15px 15px -10px; | ||
} | ||
|
||
#attachments h2 span { | ||
background-color: #fff; | ||
margin-left: 10px; | ||
} | ||
|
||
table#attachments-table { | ||
width: 100%; | ||
border-collapse: collapse; | ||
} | ||
|
||
#attachments-table td:nth-of-type(2) { | ||
text-align: right; | ||
} | ||
|
||
table#attachments-table tr td { | ||
border-bottom: 1px solid rgb(87, 87, 87); | ||
padding: 5px 0; | ||
} | ||
|
||
table#attachments-table tr:last-child td { | ||
border: none; | ||
} | ||
|
||
ul.br-list { | ||
margin: 0; | ||
padding: 0; | ||
list-style: none; | ||
} | ||
|
||
.mono { | ||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; | ||
font-size: 0.8em; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="header"> | ||
<ul class="br-list"> | ||
<li><strong>Subject:</strong> {{subject}}</li> | ||
{{#if from}} | ||
<li><strong>From:</strong> {{from}}</li> | ||
{{/if}} | ||
{{#if date}} | ||
<li><strong>Date:</strong> {{date}}</li> | ||
{{/if}} | ||
{{#if to}} | ||
<li><strong>To:</strong> {{to}}</li> | ||
{{/if}} | ||
{{#if cc}} | ||
<li><strong>CC:</strong> {{cc}}</li> | ||
{{/if}} | ||
{{#if bcc}} | ||
<li><strong>BCC:</strong> {{bcc}}</li> | ||
{{/if}} | ||
{{#if priority}} | ||
<li><strong>Priority:</strong> {{priority}}</li> | ||
{{/if}} | ||
</ul> | ||
</div> | ||
|
||
<div id="body" {{#unless has_html_body}}class="mono" {{/unless}}> | ||
{{{body}}} | ||
</div> | ||
{{#if attachments}} | ||
<div id="attachments"> | ||
<h2><span>Attachments:</span></h2> | ||
<table id="attachments-table"> | ||
{{#each attachments}} | ||
<tr> | ||
<td>{{this.filename}}</td> | ||
<td>{{this.prettySize}}</td> | ||
</tr> | ||
{{/each}} | ||
</table> | ||
</div> | ||
{{/if}} | ||
</body> | ||
</html> |
Oops, something went wrong.