Clean mailbox preview rendering

This commit is contained in:
DonVoo 2026-07-13 07:46:07 +02:00
parent 647ce4bc90
commit 8823b431f4
2 changed files with 132 additions and 4 deletions

View file

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"fmt"
"html"
"io"
"mime"
"mime/multipart"
@ -205,9 +206,127 @@ func extractTextBody(h headerGetter, body []byte) string {
}
return ""
}
if strings.HasPrefix(mediaType, "text/html") || looksLikeHTML(decoded) {
return htmlToText(string(decoded))
}
return string(decoded)
}
func looksLikeHTML(body []byte) bool {
s := strings.ToLower(strings.TrimSpace(string(body)))
return strings.HasPrefix(s, "<!doctype html") || strings.HasPrefix(s, "<html") || strings.Contains(s, "<body")
}
func htmlToText(s string) string {
s = stripHTMLBlock(s, "script")
s = stripHTMLBlock(s, "style")
s = markHTMLBreaks(s)
var b strings.Builder
inTag := false
lastSpace := false
for _, r := range s {
switch r {
case '<':
inTag = true
if !lastSpace {
b.WriteByte(' ')
lastSpace = true
}
case '>':
inTag = false
default:
if inTag {
continue
}
if r == '\n' {
b.WriteByte('\n')
lastSpace = true
continue
}
if r == '\r' || r == '\t' || r == ' ' {
if !lastSpace {
b.WriteByte(' ')
lastSpace = true
}
continue
}
b.WriteRune(r)
lastSpace = false
}
}
text := html.UnescapeString(b.String())
lines := strings.Split(text, "\n")
out := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
out = append(out, line)
}
}
if len(out) == 0 {
return strings.TrimSpace(text)
}
return strings.Join(out, "\n")
}
func markHTMLBreaks(s string) string {
repls := []struct {
Needle string
With string
}{
{"<br>", "\n"},
{"<br/>", "\n"},
{"<br />", "\n"},
{"</p>", "\n"},
{"</div>", "\n"},
{"</tr>", "\n"},
{"</td>", " "},
{"</th>", " "},
{"</li>", "\n"},
}
for _, repl := range repls {
s = replaceCaseInsensitive(s, repl.Needle, repl.With)
}
return s
}
func replaceCaseInsensitive(s, old, new string) string {
var b strings.Builder
lower := strings.ToLower(s)
needle := strings.ToLower(old)
for {
i := strings.Index(lower, needle)
if i < 0 {
b.WriteString(s)
return b.String()
}
b.WriteString(s[:i])
b.WriteString(new)
cut := i + len(old)
s = s[cut:]
lower = lower[cut:]
}
}
func stripHTMLBlock(s, tag string) string {
lower := strings.ToLower(s)
open := "<" + tag
close := "</" + tag + ">"
for {
start := strings.Index(lower, open)
if start < 0 {
return s
}
end := strings.Index(lower[start:], close)
if end < 0 {
return s[:start]
}
end += start + len(close)
s = s[:start] + " " + s[end:]
lower = strings.ToLower(s)
}
}
func decodeTransfer(body []byte, enc string) []byte {
switch strings.ToLower(strings.TrimSpace(enc)) {
case "quoted-printable":