@postalsys/email-text-tools 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const mimeHtml = require('../lib/mime-html');
4
+ const fs = require('fs');
5
+
6
+ const type = process.argv[3] || 'html';
7
+ const file = fs.readFileSync(process.argv[2]);
8
+
9
+ const HTML_TEMPLATE = `<html>
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <title>test email</title>
13
+
14
+ <style>
15
+
16
+ body {
17
+ background: white;
18
+ }
19
+
20
+ .body-container {
21
+ max-width: 840px;
22
+ margin: 30px auto;
23
+ padding: 20px;
24
+ background: #fafafa;
25
+ }
26
+
27
+ .email-container {
28
+ padding: 20px;
29
+ border: 1px solid #ccc;
30
+ background: white;
31
+ }
32
+
33
+ </style>
34
+
35
+ <head>
36
+ <body>
37
+
38
+ <div class="body-container">
39
+
40
+ <h1>Email message</h1>
41
+
42
+ <div class="email-container">
43
+ HTML_BODY
44
+ </div>
45
+
46
+ <p>Footer text</p>
47
+
48
+ </div>
49
+
50
+ </body>
51
+ </html>`;
52
+
53
+ console.log(HTML_TEMPLATE.replace(/HTML_BODY/g, mimeHtml({ [type]: file })));
package/index.js CHANGED
@@ -4,5 +4,6 @@ const textToHtml = require('./lib/text-to-html');
4
4
  const htmlToText = require('./lib/html-to-text');
5
5
  const inlineHtml = require('./lib/inline-html');
6
6
  const inlineText = require('./lib/inline-text');
7
+ const mimeHtml = require('./lib/mime-html');
7
8
 
8
- module.exports = { textToHtml, htmlToText, inlineHtml, inlineText };
9
+ module.exports = { textToHtml, htmlToText, inlineHtml, inlineText, mimeHtml };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const createDOMPurify = require('dompurify');
4
+ const { JSDOM } = require('jsdom');
5
+ const juice = require('juice');
6
+ const he = require('he');
7
+
8
+ const textToHtml = require('./text-to-html');
9
+
10
+ // define styles for plaintext emails
11
+ const INLINE_STYLE_BLOCK = `<style>
12
+
13
+ body, td, th, p {
14
+ font-family: sans-serif;
15
+ font-size: 12px;
16
+ }
17
+
18
+ blockquote {
19
+ border-left-width: 2px;
20
+ border-left-style: solid;
21
+
22
+ border-left-color: darkblue;
23
+ color: darkblue;
24
+
25
+ margin: 1rem 0;
26
+ padding-left: 1rem;
27
+ }
28
+
29
+ blockquote blockquote{
30
+ border-left-color: royalblue;
31
+ color: royalblue;
32
+ }
33
+
34
+ blockquote blockquote blockquote{
35
+ border-left-color: dodgerblue;
36
+ color: dodgerblue;
37
+ }
38
+
39
+ blockquote blockquote blockquote blockquote{
40
+ border-left-color: darkblue;
41
+ color: darkblue;
42
+ }
43
+
44
+ </style>`;
45
+
46
+ function mimeHtml(message) {
47
+ let html = message.html;
48
+ if (!html && message.text) {
49
+ html = textToHtml(message.text) || '';
50
+ if (html) {
51
+ html = INLINE_STYLE_BLOCK + html;
52
+ }
53
+ }
54
+
55
+ try {
56
+ //inline styles
57
+ //html = juice(html);
58
+ } catch (err) {
59
+ // should log?
60
+ }
61
+
62
+ const window = new JSDOM('').window;
63
+ const DOMPurify = createDOMPurify(window);
64
+
65
+ // first pass, outputs a fixed HTML page
66
+ html = DOMPurify.sanitize(html, { WHOLE_DOCUMENT: true, FORCE_BODY: false, FORBID_TAGS: ['title', 'link', 'meta'] });
67
+
68
+ try {
69
+ //inline styles
70
+ html = juice(html);
71
+ } catch (err) {
72
+ // not so important, so we'll ignore if style inlining fails
73
+ // should log?
74
+ }
75
+
76
+ // second pass, outputs a BODY DOM element
77
+ const dom = DOMPurify.sanitize(html, { RETURN_DOM: true, WHOLE_DOCUMENT: false, FORCE_BODY: true, FORBID_TAGS: ['style', 'title', 'link', 'meta'] });
78
+
79
+ // mark all links to be opened in a new window
80
+ for (let elm of dom.querySelectorAll('a')) {
81
+ elm.setAttribute('target', '_blank');
82
+ }
83
+
84
+ dom.style.overflow = 'auto';
85
+
86
+ // return <BODY> HTML
87
+
88
+ dom.style.removeProperty('width');
89
+ dom.style.removeProperty('min-width');
90
+ dom.style.removeProperty('max-width');
91
+ dom.style.removeProperty('height');
92
+ dom.style.removeProperty('min-height');
93
+ dom.style.removeProperty('max-height');
94
+ let bodyStyles = (dom.getAttribute('style') || '').toString().trim();
95
+
96
+ // embed into a styled container
97
+ return `<div style="${he.encode(bodyStyles)}">${dom.innerHTML.trim()}</div>`;
98
+ }
99
+
100
+ module.exports = mimeHtml;
@@ -101,9 +101,10 @@ function linkifyAndHtmlEscape(text) {
101
101
  return parts
102
102
  .map(part => {
103
103
  switch (part.type) {
104
- case 'text':
104
+ case 'text': {
105
105
  // normal text, escape HTML
106
106
  return he.encode(part.content, { useNamedReferences: true });
107
+ }
107
108
  case 'link':
108
109
  // URL with html escaped text content and URL
109
110
  return `<a href="${he.encode(part.url, { useNamedReferences: true })}">${he.encode(part.text, {
@@ -132,9 +133,18 @@ function textLinesToHtml(lines) {
132
133
  textparts.push('</p><p>');
133
134
  }
134
135
  } else if (i < lines.length - 1 && lines[i + 1].trim()) {
135
- textparts.push(`${linkifyAndHtmlEscape(line)}<br />`);
136
+ let escaped = linkifyAndHtmlEscape(line);
137
+ if (/^[ \t]*[-_]+[ \t]*$/.test(escaped)) {
138
+ textparts.push(`<hr />`);
139
+ } else {
140
+ textparts.push(`${escaped}<br />`);
141
+ }
136
142
  } else {
137
- textparts.push(`${linkifyAndHtmlEscape(line)}`);
143
+ let escaped = linkifyAndHtmlEscape(line);
144
+ if (/^[ \t]*[-_]+[ \t]*$/.test(escaped)) {
145
+ escaped = `<hr />`;
146
+ }
147
+ textparts.push(escaped);
138
148
  }
139
149
  }
140
150
 
@@ -142,6 +152,7 @@ function textLinesToHtml(lines) {
142
152
  }
143
153
 
144
154
  function textToHtml(text) {
155
+ text = (text || '').toString();
145
156
  let tree = generateQuotedTree(text);
146
157
 
147
158
  let blockQuoteCounter = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postalsys/email-text-tools",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Email text tools",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,10 +22,13 @@
22
22
  "eslint-config-prettier": "8.5.0"
23
23
  },
24
24
  "dependencies": {
25
+ "dompurify": "3.0.1",
25
26
  "handlebars": "4.7.7",
26
27
  "he": "1.2.0",
27
28
  "html-to-text": "8.2.1",
28
29
  "js-beautify": "1.14.7",
30
+ "jsdom": "21.1.0",
31
+ "juice": "8.1.0",
29
32
  "linkify-it": "4.0.1",
30
33
  "moment-timezone": "0.5.39",
31
34
  "node-html-parser": "6.1.4",