html.go

 1package view
 2
 3import (
 4	"fmt"
 5	"io"
 6	"mime/quotedprintable"
 7	"strings"
 8
 9	"github.com/PuerkitoBio/goquery"
10)
11
12// hyperlink formats a string as a terminal-clickable hyperlink.
13func hyperlink(url, text string) string {
14	if text == "" {
15		text = url
16	}
17	return fmt.Sprintf("\x1b]8;;%s\x07%s\x1b]8;;\x07", url, text)
18}
19
20func decodeQuotedPrintable(s string) (string, error) {
21	reader := quotedprintable.NewReader(strings.NewReader(s))
22	body, err := io.ReadAll(reader)
23	if err != nil {
24		return "", err
25	}
26	return string(body), nil
27}
28
29// ProcessBody takes a raw email body, decodes it, and formats it as plain
30// text with terminal hyperlinks.
31func ProcessBody(rawBody string) (string, error) {
32	decodedBody, err := decodeQuotedPrintable(rawBody)
33	if err != nil {
34		// If decoding fails, fallback to the raw body
35		decodedBody = rawBody
36	}
37
38	doc, err := goquery.NewDocumentFromReader(strings.NewReader(decodedBody))
39	if err != nil {
40		return "", fmt.Errorf("could not parse email body: %w", err)
41	}
42
43	// Remove style and script tags to clean up the view
44	doc.Find("style, script").Remove()
45
46	// Add newlines after block elements for better spacing
47	doc.Find("p, div, h1, h2, h3, h4, h5, h6").Each(func(i int, s *goquery.Selection) {
48		s.After("\n\n")
49	})
50
51	// Replace <br> tags with newlines
52	doc.Find("br").Each(func(i int, s *goquery.Selection) {
53		s.ReplaceWithHtml("\n")
54	})
55
56	// Format links into terminal hyperlinks
57	doc.Find("a").Each(func(i int, s *goquery.Selection) {
58		href, exists := s.Attr("href")
59		if !exists {
60			return
61		}
62		s.ReplaceWithHtml(hyperlink(href, s.Text()))
63	})
64
65	// Return the document's text content, which now includes our formatting
66	return doc.Text(), nil
67}