forked from pocketbase/pocketbase
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added auto html to plain text mail generation
- Loading branch information
1 parent
f14105b
commit 0f9ddbf
Showing
13 changed files
with
255 additions
and
14 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 |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package mailer | ||
|
||
import ( | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/pocketbase/pocketbase/tools/list" | ||
"golang.org/x/net/html" | ||
) | ||
|
||
var whitespaceRegex = regexp.MustCompile(`\s+`) | ||
|
||
// Very rudimentary auto HTML to Text mail body converter. | ||
// | ||
// Caveats: | ||
// - This method doesn't check for correctness of the HTML document. | ||
// - Links will be converted to "[text](url)" format. | ||
// - List items (<li>) are prefixed with "- ". | ||
// - Indentation is stripped (both tabs and spaces). | ||
// - Trailing spaces are preserved. | ||
// - Multiple consequence newlines are collapsed as one unless multiple <br> tags are used. | ||
func html2Text(htmlDocument string) (string, error) { | ||
var builder strings.Builder | ||
|
||
doc, err := html.Parse(strings.NewReader(htmlDocument)) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
tagsToSkip := []string{ | ||
"style", "script", "iframe", "applet", "object", "svg", "img", | ||
"button", "form", "textarea", "input", "select", "option", "template", | ||
} | ||
|
||
inlineTags := []string{ | ||
"a", "span", "small", "strike", "strong", | ||
"sub", "sup", "em", "b", "u", "i", | ||
} | ||
|
||
var canAddNewLine bool | ||
|
||
// see https://pkg.go.dev/golang.org/x/net/html#Parse | ||
var f func(*html.Node) | ||
f = func(n *html.Node) { | ||
// start link wrapping for producing "[text](link)" formatted string | ||
isLink := n.Type == html.ElementNode && n.Data == "a" | ||
if isLink { | ||
builder.WriteString("[") | ||
} | ||
|
||
switch n.Type { | ||
case html.TextNode: | ||
txt := whitespaceRegex.ReplaceAllString(n.Data, " ") | ||
|
||
// the prev node has new line so it is safe to trim the indentation | ||
if !canAddNewLine { | ||
txt = strings.TrimLeft(txt, " ") | ||
} | ||
|
||
if txt != "" { | ||
builder.WriteString(txt) | ||
canAddNewLine = true | ||
} | ||
case html.ElementNode: | ||
if n.Data == "br" { | ||
// always write new lines when <br> tag is used | ||
builder.WriteString("\r\n") | ||
canAddNewLine = false | ||
} else if canAddNewLine && !list.ExistInSlice(n.Data, inlineTags) { | ||
builder.WriteString("\r\n") | ||
canAddNewLine = false | ||
} | ||
|
||
// prefix list items with dash | ||
if n.Data == "li" { | ||
builder.WriteString("- ") | ||
} | ||
} | ||
|
||
for c := n.FirstChild; c != nil; c = c.NextSibling { | ||
if c.Type != html.ElementNode || !list.ExistInSlice(c.Data, tagsToSkip) { | ||
f(c) | ||
} | ||
} | ||
|
||
// end link wrapping | ||
if isLink { | ||
builder.WriteString("]") | ||
for _, a := range n.Attr { | ||
if a.Key == "href" { | ||
if a.Val != "" { | ||
builder.WriteString("(") | ||
builder.WriteString(a.Val) | ||
builder.WriteString(")") | ||
} | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
f(doc) | ||
|
||
return strings.TrimSpace(builder.String()), nil | ||
} |
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,131 @@ | ||
package mailer | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestHtml2Text(t *testing.T) { | ||
scenarios := []struct { | ||
html string | ||
expected string | ||
}{ | ||
{ | ||
"", | ||
"", | ||
}, | ||
{ | ||
"ab c", | ||
"ab c", | ||
}, | ||
{ | ||
"<!-- test html comment -->", | ||
"", | ||
}, | ||
{ | ||
"<!-- test html comment --> a ", | ||
"a", | ||
}, | ||
{ | ||
"<span>a</span>b<span>c</span>", | ||
"abc", | ||
}, | ||
{ | ||
`<a href="a/b/c">test</span>`, | ||
"[test](a/b/c)", | ||
}, | ||
{ | ||
`<a href="">test</span>`, | ||
"[test]", | ||
}, | ||
{ | ||
"<span>a</span> <span>b</span>", | ||
"a b", | ||
}, | ||
{ | ||
"<span>a</span> b <span>c</span>", | ||
"a b c", | ||
}, | ||
{ | ||
"<span>a</span> b <div>c</div>", | ||
"a b \r\nc", | ||
}, | ||
{ | ||
` | ||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
<html xmlns="http://www.w3.org/1999/xhtml"> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
<style> | ||
body { | ||
padding: 0; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<!-- test html comment --> | ||
<style> | ||
body { | ||
padding: 0; | ||
} | ||
</style> | ||
<div class="wrapper"> | ||
<div class="content"> | ||
<p>Lorem ipsum</p> | ||
<p>Dolor sit amet</p> | ||
<p> | ||
<a href="a/b/c">Verify</a> | ||
</p> | ||
<br> | ||
<p> | ||
<a href="a/b/c"><strong>Verify2.1</strong> <strong>Verify2.2</strong></a> | ||
</p> | ||
<br> | ||
<br> | ||
<div> | ||
<div> | ||
<div> | ||
<ul> | ||
<li>ul.test1</li> | ||
<li>ul.test2</li> | ||
<li>ul.test3</li> | ||
</ul> | ||
<ol> | ||
<li>ol.test1</li> | ||
<li>ol.test2</li> | ||
<li>ol.test3</li> | ||
</ol> | ||
</div> | ||
</div> | ||
</div> | ||
<select> | ||
<option>Option 1</option> | ||
<option>Option 2</option> | ||
</select> | ||
<textarea>test</textarea> | ||
<input type="text" value="test" /> | ||
<button>test</button> | ||
<p> | ||
Thanks,<br/> | ||
PocketBase team | ||
</p> | ||
</div> | ||
</div> | ||
</body> | ||
</html> | ||
`, | ||
"Lorem ipsum \r\nDolor sit amet \r\n[Verify](a/b/c) \r\n[Verify2.1 Verify2.2](a/b/c) \r\n\r\n- ul.test1 \r\n- ul.test2 \r\n- ul.test3 \r\n- ol.test1 \r\n- ol.test2 \r\n- ol.test3 \r\nThanks,\r\nPocketBase team", | ||
}, | ||
} | ||
|
||
for i, s := range scenarios { | ||
result, err := html2Text(s.html) | ||
if err != nil { | ||
t.Errorf("(%d) Unexpected error %v", i, err) | ||
} | ||
|
||
if result != s.expected { | ||
t.Errorf("(%d) Expected \n(%q)\n%v,\n\ngot:\n\n(%q)\n%v", i, s.expected, s.expected, result, result) | ||
} | ||
} | ||
} |
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 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
2 changes: 1 addition & 1 deletion
2
ui/dist/assets/CodeEditor.ee0ff101.js → ui/dist/assets/CodeEditor.f01bd0e9.js
Large diffs are not rendered by default.
Oops, something went wrong.
Oops, something went wrong.