Skip to content
22 changes: 15 additions & 7 deletions internal/htmlutil/htmlutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,18 @@ func walkNode(b *strings.Builder, n *html.Node) {
return
case "figure":
if att := parseTrixAttachment(n); att != nil {
fmt.Fprintf(b, "\n[%s]\n", att.Filename)
return
if att.Filename != "" {
fmt.Fprintf(b, "\n[%s]\n", att.Filename)
return
}
// HEY wraps pasted rich HTML in text/html trix attachments
// whose markup sits in the JSON "content" field. Render it.
if att.Content != "" {
if doc, err := html.Parse(strings.NewReader(att.Content)); err == nil {
walkNode(b, doc)
return
}
}
}
case "p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote":
b.WriteString("\n")
Expand Down Expand Up @@ -126,6 +136,7 @@ type trixAttachment struct {
ContentType string `json:"contentType"`
Filesize *int64 `json:"filesize"`
SGID string `json:"sgid"`
Content string `json:"content"`
}

func parseTrixAttachment(n *html.Node) *trixAttachment {
Expand All @@ -137,9 +148,6 @@ func parseTrixAttachment(n *html.Node) *trixAttachment {
if err := json.Unmarshal([]byte(raw), &att); err != nil {
return nil
}
if att.Filename == "" {
return nil
}
return &att
}

Expand Down Expand Up @@ -174,7 +182,7 @@ func findAttachments(n *html.Node, attachments *[]Attachment) {
*attachments = append(*attachments, attachment)
}
case "figure":
if trix := parseTrixAttachment(n); trix != nil && trix.URL != "" {
if trix := parseTrixAttachment(n); trix != nil && trix.URL != "" && trix.Filename != "" {
*attachments = append(*attachments, Attachment{
URL: trix.URL,
Filename: trix.Filename,
Expand Down Expand Up @@ -227,7 +235,7 @@ func findImages(n *html.Node, urls *[]string) {
*urls = append(*urls, imageURL)
}
case "figure":
if att := parseTrixAttachment(n); att != nil && att.URL != "" && isImageContentType(att.ContentType) {
if att := parseTrixAttachment(n); att != nil && att.URL != "" && att.Filename != "" && isImageContentType(att.ContentType) {
*urls = append(*urls, att.URL)
}
}
Expand Down
22 changes: 22 additions & 0 deletions internal/htmlutil/htmlutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,25 @@ func TestExtractImageURLsTrixFigure(t *testing.T) {
t.Errorf("url[0] = %q, want %q", urls[0], "/rails/blobs/abc/image.png")
}
}

func TestToTextRendersInlineHTMLTrixAttachments(t *testing.T) {
// HEY wraps pasted rich HTML in text/html trix attachments: the markup
// sits inside the JSON attribute, and the figure element has no children.
content := `<figure data-trix-attachment="{&quot;contentType&quot;:&quot;text/html&quot;,&quot;content&quot;:&quot;<shadow-content><template><p>Please join us for the parent retreat on Saturday.</p></template></shadow-content>&quot;,&quot;data&quot;:&quot;{}&quot;}"></figure>` +
`<figure data-trix-attachment="{&quot;contentType&quot;:&quot;text/html&quot;,&quot;content&quot;:&quot;<shadow-content><template><p>RSVP to maria.gonzalez@example.org by Friday.</p></template></shadow-content>&quot;,&quot;data&quot;:&quot;{}&quot;}"></figure>`

got := ToText(content)
if !strings.Contains(got, "Please join us for the parent retreat on Saturday.") {
t.Errorf("ToText dropped the first inline HTML segment: %q", got)
}
if !strings.Contains(got, "RSVP to maria.gonzalez@example.org by Friday.") {
t.Errorf("ToText dropped the second inline HTML segment: %q", got)
}
}

func TestToTextKeepsFileAttachmentPlaceholders(t *testing.T) {
content := `<figure data-trix-attachment="{&quot;contentType&quot;:&quot;application/pdf&quot;,&quot;filename&quot;:&quot;retreat-schedule.pdf&quot;,&quot;url&quot;:&quot;/attachments/12&quot;}"></figure>`
if got := ToText(content); !strings.Contains(got, "[retreat-schedule.pdf]") {
t.Errorf("ToText should keep the filename placeholder: %q", got)
}
}
6 changes: 3 additions & 3 deletions internal/tui/bulk_reply.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (f *bulkReplyForm) previewContent(width int) string {
}
b.WriteString("\n\n")

labelStyle := lipgloss.NewStyle().Foreground(colorMuted)
labelStyle := styleMuted
for i, entry := range f.draft.Entries {
writeBulkReplyWrappedLine(&b, fmt.Sprintf("%d. ", i+1), terminalSafeAttachmentText(entry.TopicName), width)
writeBulkReplyContacts(&b, "To", entry.Addressed.Directly, width, labelStyle)
Expand All @@ -171,12 +171,12 @@ func (f *bulkReplyForm) previewContent(width int) string {
func (f *bulkReplyForm) composeView() string {
var b strings.Builder
fmt.Fprintf(&b, "%s\n", f.styles.title.Render(fmt.Sprintf("Bulk reply to %d %s", len(f.draft.Entries), tuiThreadNoun(len(f.draft.Entries)))))
b.WriteString(lipgloss.NewStyle().Foreground(colorMuted).Render("The exact recipients from the preview and HEY's name tag will be preserved."))
b.WriteString(styleMuted.Render("The exact recipients from the preview and HEY's name tag will be preserved."))
b.WriteString("\n\n")
b.WriteString(f.body.View())
b.WriteString("\n")
if f.status != "" {
statusStyle := lipgloss.NewStyle().Foreground(colorMuted)
statusStyle := styleMuted
if f.isError {
statusStyle = lipgloss.NewStyle().Foreground(colorError)
}
Expand Down
8 changes: 4 additions & 4 deletions internal/tui/calendar_views.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ type placedEvent struct {

func renderDayView(events, todos, habits []models.Recording, _ time.Time, width, _ int) string {
var b strings.Builder
muted := lipgloss.NewStyle().Foreground(colorMuted)
muted := styleMuted
primary := lipgloss.NewStyle().Foreground(colorPrimary)

// Habits ribbon above columns
Expand Down Expand Up @@ -427,7 +427,7 @@ type weekDayInfo struct {

func renderWeekView(events, todos, habits []models.Recording, anchor time.Time, firstWeekDay time.Weekday, width, _ int, dayLabels map[string]string) string {
var b strings.Builder
muted := lipgloss.NewStyle().Foreground(colorMuted)
muted := styleMuted
bright := lipgloss.NewStyle().Foreground(colorBright)
primary := lipgloss.NewStyle().Foreground(colorPrimary)

Expand Down Expand Up @@ -603,10 +603,10 @@ func weekDayColumnLabel(d time.Time, isFirstCol bool) string {

func renderYearView(events []models.Recording, anchor time.Time, firstWeekDay time.Weekday, width, _ int, dayLabels map[string]string) string {
var b strings.Builder
muted := lipgloss.NewStyle().Foreground(colorMuted)
muted := styleMuted
bright := lipgloss.NewStyle().Foreground(colorBright)
primary := lipgloss.NewStyle().Foreground(colorPrimary).Bold(true)
faint := lipgloss.NewStyle().Foreground(colorMuted).Faint(true)
faint := styleMuted.Foreground(colorMuted) // extra-dim filler days outside the year

loc := anchor.Location()
yearStart := time.Date(anchor.Year(), 1, 1, 0, 0, 0, 0, loc)
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func (f *composeForm) view() string {
b.WriteString("\n")

labels := []string{"To", "Cc", "Bcc", "Subject"}
labelStyle := lipgloss.NewStyle().Foreground(colorMuted)
labelStyle := styleMuted
for i := range f.inputs {
b.WriteString(labelStyle.Render(fmt.Sprintf("%8s: ", labels[i])))
b.WriteString(f.inputs[i].View())
Expand All @@ -291,7 +291,7 @@ func (f *composeForm) view() string {
}

if f.status != "" {
st := lipgloss.NewStyle().Foreground(colorMuted)
st := styleMuted
if f.isError {
st = lipgloss.NewStyle().Foreground(colorError)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/contact_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (f *contactForm) view() string {
title = "Edit contact"
}
labels := []string{"Name", "Email", "Aliases"}
labelStyle := lipgloss.NewStyle().Foreground(colorMuted)
labelStyle := styleMuted
var b strings.Builder
b.WriteString(f.styles.title.Render(title))
b.WriteString("\n\n")
Expand Down Expand Up @@ -221,7 +221,7 @@ func (f *contactNoteForm) view() string {
b.WriteString("\n\n")
b.WriteString(f.input.View())
if f.status != "" {
style := lipgloss.NewStyle().Foreground(colorMuted)
style := styleMuted
if f.isError {
style = lipgloss.NewStyle().Foreground(colorError)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/contacts_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ func (l *contactList) remove(id int64) {

func (l *contactList) view() string {
if len(l.contacts) == 0 {
return lipgloss.NewStyle().Foreground(colorMuted).Render(" (no contacts)")
return styleMuted.Render(" (no contacts)")
}
visible := max(l.height/2, 1)
end := min(l.scrollOff+visible, len(l.contacts))
selected := lipgloss.NewStyle().Foreground(colorPrimary).Bold(true)
normal := lipgloss.NewStyle().Foreground(colorBright)
muted := lipgloss.NewStyle().Foreground(colorMuted)
muted := styleMuted

var b strings.Builder
for i := l.scrollOff; i < end; i++ {
Expand Down
Loading
Loading