diff --git a/internal/htmlutil/htmlutil.go b/internal/htmlutil/htmlutil.go
index 4d69dcc30..f710bdb49 100644
--- a/internal/htmlutil/htmlutil.go
+++ b/internal/htmlutil/htmlutil.go
@@ -106,8 +106,9 @@ func walkNode(b *strings.Builder, n *html.Node, depth int) {
if isImageContentType(getAttr(n, "content-type")) && isDecorativeImage(getAttr(n, "width"), getAttr(n, "height")) {
return
}
- filename := getAttr(n, "filename")
- if filename != "" {
+ if doc := parseEmbeddedActionText(n, depth); doc != nil {
+ walkNode(b, doc, depth+1)
+ } else if filename := getAttr(n, "filename"); filename != "" {
fmt.Fprintf(b, "\n[%s]\n", filename)
}
return
@@ -172,6 +173,13 @@ func walkMessageSourceNode(b *strings.Builder, n *html.Node, depth int) {
case "hr":
writeMessageSourceBoundary(b)
return
+ case "action-text-attachment":
+ if doc := parseEmbeddedActionText(n, depth); doc != nil {
+ writeMessageSourceBoundary(b)
+ walkMessageSourceNode(b, doc, depth+1)
+ writeMessageSourceBoundary(b)
+ }
+ return
case "template":
if depth == 0 || n.Parent == nil || n.Parent.Data != "shadow-content" {
return
@@ -206,7 +214,7 @@ func elementProvidesMessageSourceText(n *html.Node) bool {
return false
}
switch n.Data {
- case "script", "style", "noscript", "head", "action-text-attachment":
+ case "script", "style", "noscript", "head":
return false
case "dialog":
return hasAttr(n, "open")
@@ -305,6 +313,24 @@ func parseTrixAttachment(n *html.Node) *trixAttachment {
return &att
}
+// embeddedActionTextContent returns the markup an unnamed text/html attachment carries
+// in its content attribute. HEY renders any other attachment as itself, whatever
+// content it carries.
+func embeddedActionTextContent(n *html.Node) string {
+ if getAttr(n, "filename") != "" || !isHTMLContentType(getAttr(n, "content-type")) {
+ return ""
+ }
+ return getAttr(n, "content")
+}
+
+func parseEmbeddedActionText(n *html.Node, depth int) *html.Node {
+ content := embeddedActionTextContent(n)
+ if content == "" {
+ return nil
+ }
+ return parseEmbeddedContent(content, depth)
+}
+
func getAttr(n *html.Node, key string) string {
for _, a := range n.Attr {
if a.Key == key {
@@ -455,7 +481,9 @@ func findImages(n *html.Node, urls *[]string, depth int) {
if isImageContentType(getAttr(n, "content-type")) && isDecorativeImage(getAttr(n, "width"), getAttr(n, "height")) {
return
}
- if imageURL := getAttr(n, "url"); isImageContentType(getAttr(n, "content-type")) && imageURL != "" {
+ if doc := parseEmbeddedActionText(n, depth); doc != nil {
+ findImages(doc, urls, depth+1)
+ } else if imageURL := getAttr(n, "url"); isImageContentType(getAttr(n, "content-type")) && imageURL != "" {
*urls = append(*urls, imageURL)
}
case "figure":
diff --git a/internal/htmlutil/htmlutil_test.go b/internal/htmlutil/htmlutil_test.go
index 7ee423004..2966f2039 100644
--- a/internal/htmlutil/htmlutil_test.go
+++ b/internal/htmlutil/htmlutil_test.go
@@ -105,6 +105,13 @@ func TestMessageSourceTextIncludesEmbeddedEmailBody(t *testing.T) {
}
}
+func TestMessageSourceTextIncludesEmbeddedActionTextAttachment(t *testing.T) {
+ html := `
Before
After
`) if !strings.Contains(got, "[photo.png]") { @@ -204,6 +218,75 @@ func TestExtractImageURLsInsideEmbeddedHTMLAttachment(t *testing.T) { } } +func TestExtractImageURLsInsideEmbeddedActionTextAttachment(t *testing.T) { + urls := ExtractImageURLs(`innermost
`
+ for range embeddedContentDepthLimit {
+ withinLimit = embeddedActionTextAttachment(withinLimit)
+ }
+ beyondLimit := embeddedActionTextAttachment(withinLimit)
+
+ for name, text := range map[string]func(string) string{"ToText": ToText, "MessageSourceText": MessageSourceText} {
+ if got := text(withinLimit); !strings.Contains(got, "innermost") {
+ t.Errorf("%s = %q, want the body at the nesting limit", name, got)
+ }
+ if got := text(beyondLimit); strings.Contains(got, "innermost") {
+ t.Errorf("%s = %q, should stop before the body beyond the nesting limit", name, got)
+ }
+ }
+ if urls := ExtractImageURLs(withinLimit); len(urls) != 1 {
+ t.Errorf("ExtractImageURLs = %v, want the image at the nesting limit", urls)
+ }
+ if urls := ExtractImageURLs(beyondLimit); len(urls) != 0 {
+ t.Errorf("ExtractImageURLs = %v, should stop before the image beyond the nesting limit", urls)
+ }
+}
+
+// HEY renders an attachment's content as HTML only when its content type is HTML; any
+// other attachment is itself, whatever content it carries.
+func TestNonHTMLActionTextAttachmentContentIsNotABody(t *testing.T) {
+ image := `Visible
innermost
" + for range embeddedContentDepthLimit { + withinLimit = embeddedActionTextAttachment(withinLimit) + } + if got := toMarkdown(withinLimit); got != "innermost" { + t.Errorf("ToMarkdown = %q, want the body at the nesting limit", got) + } + + if got := toMarkdown(embeddedActionTextAttachment(withinLimit)); strings.Contains(got, "innermost") { + t.Errorf("ToMarkdown = %q, should stop before the body beyond the nesting limit", got) + } +} + func TestToMarkdownEmbeddedContentStopsRecursing(t *testing.T) { nested := "innermost
" for range embeddedContentDepthLimit + 1 {