136 lines
3.9 KiB
Go
136 lines
3.9 KiB
Go
package mujica
|
||
|
||
import (
|
||
"log/slog"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"mujicaui/theme"
|
||
)
|
||
|
||
// MailFormat selects plain text or safe Markdown.
|
||
type MailFormat int
|
||
|
||
const (
|
||
MailPlain MailFormat = iota
|
||
// MailMarkdown uses MarkdownView’s safe subset.
|
||
MailMarkdown
|
||
)
|
||
|
||
// MailAttachment is a file sent with a mail.
|
||
type MailAttachment struct {
|
||
Name string
|
||
Size int64
|
||
Type string
|
||
}
|
||
|
||
// Mail supplies headers, body and attachments.
|
||
type Mail struct {
|
||
From string
|
||
To, Cc []string
|
||
Subject string
|
||
Date time.Time
|
||
Body string
|
||
Format MailFormat
|
||
Attachments []MailAttachment
|
||
}
|
||
|
||
// MailReaderResult is a MailReader's element and this frame's events.
|
||
type MailReaderResult struct {
|
||
Element *ui.Element
|
||
// Link contains the activated URL, or an empty string.
|
||
Link string
|
||
// Opened and Downloaded are attachment indexes, -1 for none.
|
||
Opened, Downloaded int
|
||
}
|
||
|
||
type mailQuotesKey struct{}
|
||
|
||
// MailReader displays headers, body, folded quotes and attachments.
|
||
func MailReader(c *ui.Context, m Mail) MailReaderResult {
|
||
if m.From == "" || m.Subject == "" {
|
||
panic("mujica: MailReader needs From and Subject")
|
||
}
|
||
if m.Format != MailPlain && m.Format != MailMarkdown {
|
||
panic("mujica: unknown MailFormat")
|
||
}
|
||
k := Tokens(c)
|
||
u := Density(c).Unit()
|
||
r := MailReaderResult{Opened: -1, Downloaded: -1}
|
||
col := ui.Column(c).Gap(u * 4).MinWidth(0).Role(ui.RoleGroup).Label(m.Subject)
|
||
r.Element = col
|
||
small := FontSize(c, theme.CaptionSize)
|
||
col.Children(func() {
|
||
ui.Text(c, m.Subject).Font(theme.SerifFont).FontSize(FontSize(c, theme.H2Size)).FontWeight(600)
|
||
ui.Row(c).Gap(u * 3).AlignItems(ui.Start).Children(func() {
|
||
Avatar(c, m.From, AvatarOptions{Size: 36})
|
||
ui.Column(c).Gap(2).Grow(1).MinWidth(0).Children(func() {
|
||
ui.Text(c, m.From).FontWeight(600).Selectable().FocusRing(false)
|
||
for _, line := range [][2]string{{"mail_reader.to", strings.Join(m.To, ", ")}, {"mail_reader.cc", strings.Join(m.Cc, ", ")}} {
|
||
if line[1] != "" {
|
||
ui.Text(c, msg(c, line[0])+" "+line[1]).FontSize(small).TextColor(k.TextMuted).Selectable().FocusRing(false)
|
||
}
|
||
}
|
||
})
|
||
if !m.Date.IsZero() {
|
||
ui.Text(c, messagingDate(c, m.Date, "mail_reader.date")).FontSize(small).TextColor(k.TextMuted).FontFeatures("tnum").SingleLine()
|
||
}
|
||
})
|
||
ui.Box(c).Height(1).Background(k.Border)
|
||
if m.Format == MailMarkdown {
|
||
if url, ok := MarkdownView(c, m.Body).Link(); ok {
|
||
r.Link = url
|
||
}
|
||
} else {
|
||
mailPlainBody(c, ui.Local(col, mailQuotesKey{}, func() map[string]bool { return map[string]bool{} }), m.Body)
|
||
}
|
||
if len(m.Attachments) > 0 {
|
||
ui.Text(c, msg(c, "mail_reader.attachments")).FontSize(small).FontWeight(600).TextColor(k.TextMuted)
|
||
ui.Row(c).Gap(u * 2).Wrap().Children(func() {
|
||
for i, a := range m.Attachments {
|
||
f := FileMessage(c, FileMessageOptions(a))
|
||
f.Element.Key(i)
|
||
if f.Opened() {
|
||
r.Opened = i
|
||
}
|
||
if f.Downloaded() {
|
||
r.Downloaded = i
|
||
}
|
||
}
|
||
})
|
||
}
|
||
})
|
||
if r.Link != "" || r.Opened >= 0 || r.Downloaded >= 0 {
|
||
slog.Debug("mujica: mail_reader action", "opened", r.Opened, "downloaded", r.Downloaded)
|
||
}
|
||
return r
|
||
}
|
||
|
||
// mailPlainBody shows plain text, folding each run of quoted lines.
|
||
func mailPlainBody(c *ui.Context, open *map[string]bool, body string) {
|
||
lines := strings.Split(body, "\n")
|
||
ui.Column(c).Gap(Density(c).Unit() * 2).MinWidth(0).Children(func() {
|
||
for i := 0; i < len(lines); {
|
||
j := i
|
||
quoted := strings.HasPrefix(lines[i], ">")
|
||
for j < len(lines) && strings.HasPrefix(lines[j], ">") == quoted {
|
||
j++
|
||
}
|
||
chunk := strings.Join(lines[i:j], "\n")
|
||
ui.Box(c).Key(i).Children(func() {
|
||
if quoted {
|
||
// Fold state is kept per quote text.
|
||
o := (*open)[chunk]
|
||
QuotedText(c, chunk, &o)
|
||
(*open)[chunk] = o
|
||
} else if strings.TrimSpace(chunk) != "" {
|
||
ui.Text(c, strings.Trim(chunk, "\n")).Selectable().FocusRing(false).LineHeight(theme.LineHeight)
|
||
}
|
||
})
|
||
i = j
|
||
}
|
||
})
|
||
}
|