@postalsys/email-text-tools 1.0.2 → 1.1.1

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,129 @@
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
+ const FORBID_TAGS = [
11
+ 'title',
12
+ 'link',
13
+ 'meta',
14
+ 'base',
15
+ 'basefont',
16
+ 'frame',
17
+ 'iframe',
18
+ 'frameset',
19
+ 'template',
20
+ 'script',
21
+ 'noframes',
22
+ 'noscript',
23
+ 'object',
24
+ 'embed',
25
+ 'dialog',
26
+ 'canvas',
27
+ 'audio',
28
+ 'video',
29
+ 'applet'
30
+ ];
31
+ // keep the style at first
32
+ const FORBID_TAGS_ALL = ['style'].concat(FORBID_TAGS);
33
+
34
+ // define styles for plaintext emails
35
+ const INLINE_STYLE_BLOCK = `<style>
36
+
37
+ body, td, th, p {
38
+ font-family: sans-serif;
39
+ font-size: 12px;
40
+ }
41
+
42
+ blockquote {
43
+ border-left-width: 2px;
44
+ border-left-style: solid;
45
+
46
+ border-left-color: darkblue;
47
+ color: darkblue;
48
+
49
+ margin: 1rem 0;
50
+ padding-left: 1rem;
51
+ }
52
+
53
+ blockquote blockquote{
54
+ border-left-color: royalblue;
55
+ color: royalblue;
56
+ }
57
+
58
+ blockquote blockquote blockquote{
59
+ border-left-color: dodgerblue;
60
+ color: dodgerblue;
61
+ }
62
+
63
+ blockquote blockquote blockquote blockquote{
64
+ border-left-color: darkblue;
65
+ color: darkblue;
66
+ }
67
+
68
+ </style>`;
69
+
70
+ function mimeHtml(message) {
71
+ let html = message.html;
72
+ if (!html && message.text) {
73
+ html = textToHtml(message.text) || '';
74
+ if (html) {
75
+ html = INLINE_STYLE_BLOCK + html;
76
+ }
77
+ }
78
+
79
+ try {
80
+ //inline styles
81
+ //html = juice(html);
82
+ } catch (err) {
83
+ // should log?
84
+ }
85
+
86
+ const window = new JSDOM('').window;
87
+ const DOMPurify = createDOMPurify(window);
88
+
89
+ // first pass, outputs a fixed HTML page
90
+ html = DOMPurify.sanitize(html, { WHOLE_DOCUMENT: true, FORCE_BODY: false, FORBID_TAGS });
91
+
92
+ try {
93
+ //inline styles
94
+ html = juice(html);
95
+ } catch (err) {
96
+ // not so important, so we'll ignore if style inlining fails
97
+ // should log?
98
+ }
99
+
100
+ // second pass, outputs a BODY DOM element
101
+ const dom = DOMPurify.sanitize(html, {
102
+ RETURN_DOM: true,
103
+ WHOLE_DOCUMENT: false,
104
+ FORCE_BODY: true,
105
+ FORBID_TAGS: FORBID_TAGS_ALL
106
+ });
107
+
108
+ // mark all links to be opened in a new window
109
+ for (let elm of dom.querySelectorAll('a')) {
110
+ elm.setAttribute('target', '_blank');
111
+ }
112
+
113
+ dom.style.overflow = 'auto';
114
+
115
+ // return <BODY> HTML
116
+
117
+ dom.style.removeProperty('width');
118
+ dom.style.removeProperty('min-width');
119
+ dom.style.removeProperty('max-width');
120
+ dom.style.removeProperty('height');
121
+ dom.style.removeProperty('min-height');
122
+ dom.style.removeProperty('max-height');
123
+ let bodyStyles = (dom.getAttribute('style') || '').toString().trim();
124
+
125
+ // embed into a styled container
126
+ return `<div style="${he.encode(bodyStyles)}">${dom.innerHTML.trim()}</div>`;
127
+ }
128
+
129
+ 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]*[-_]{2,}[ \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]*[-_]{2,}[ \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.1",
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",