1package tui
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/charmbracelet/bubbles/viewport"
8 tea "github.com/charmbracelet/bubbletea"
9 "github.com/charmbracelet/lipgloss"
10 "github.com/floatpane/matcha/fetcher"
11 "github.com/floatpane/matcha/view"
12)
13
14var (
15 emailHeaderStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).Padding(0, 1)
16 attachmentBoxStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, false, true).PaddingLeft(2).MarginTop(1)
17)
18
19type EmailView struct {
20 viewport viewport.Model
21 email fetcher.Email
22 emailIndex int
23 attachmentCursor int
24 focusOnAttachments bool
25}
26
27func NewEmailView(email fetcher.Email, emailIndex, width, height int) *EmailView {
28 // Pass the styles from the tui package to the view package
29 body, err := view.ProcessBody(email.Body, H1Style, H2Style, BodyStyle)
30 if err != nil {
31 body = fmt.Sprintf("Error rendering body: %v", err)
32 }
33
34 header := fmt.Sprintf("From: %s\nSubject: %s", email.From, email.Subject)
35 headerHeight := lipgloss.Height(header) + 2
36
37 attachmentHeight := 0
38 if len(email.Attachments) > 0 {
39 attachmentHeight = len(email.Attachments) + 2
40 }
41
42 vp := viewport.New(width, height-headerHeight-attachmentHeight)
43 vp.SetContent(body)
44
45 return &EmailView{
46 viewport: vp,
47 email: email,
48 emailIndex: emailIndex,
49 }
50}
51
52func (m *EmailView) Init() tea.Cmd {
53 return nil
54}
55
56func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
57 var cmd tea.Cmd
58 var cmds []tea.Cmd
59
60 switch msg := msg.(type) {
61 case tea.KeyMsg:
62 // Handle 'esc' key locally
63 if msg.Type == tea.KeyEsc {
64 if m.focusOnAttachments {
65 m.focusOnAttachments = false
66 return m, nil
67 }
68 return m, func() tea.Msg { return BackToInboxMsg{} }
69 }
70
71 if m.focusOnAttachments {
72 switch msg.String() {
73 case "up", "k":
74 if m.attachmentCursor > 0 {
75 m.attachmentCursor--
76 }
77 case "down", "j":
78 if m.attachmentCursor < len(m.email.Attachments)-1 {
79 m.attachmentCursor++
80 }
81 case "enter":
82 if len(m.email.Attachments) > 0 {
83 selected := m.email.Attachments[m.attachmentCursor]
84 idx := m.emailIndex
85 return m, func() tea.Msg {
86 return DownloadAttachmentMsg{
87 Index: idx,
88 Filename: selected.Filename,
89 PartID: selected.PartID,
90 Data: selected.Data,
91 }
92 }
93 }
94 case "tab":
95 m.focusOnAttachments = false
96 }
97 } else {
98 switch msg.String() {
99 case "r":
100 return m, func() tea.Msg { return ReplyToEmailMsg{Email: m.email} }
101 case "tab":
102 if len(m.email.Attachments) > 0 {
103 m.focusOnAttachments = true
104 }
105 }
106 }
107 case tea.WindowSizeMsg:
108 header := fmt.Sprintf("From: %s\nSubject: %s", m.email.From, m.email.Subject)
109 headerHeight := lipgloss.Height(header) + 2
110 attachmentHeight := 0
111 if len(m.email.Attachments) > 0 {
112 attachmentHeight = len(m.email.Attachments) + 2
113 }
114 m.viewport.Width = msg.Width
115 m.viewport.Height = msg.Height - headerHeight - attachmentHeight
116 }
117
118 m.viewport, cmd = m.viewport.Update(msg)
119 cmds = append(cmds, cmd)
120
121 return m, tea.Batch(cmds...)
122}
123
124func (m *EmailView) View() string {
125 header := fmt.Sprintf("From: %s | Subject: %s", m.email.From, m.email.Subject)
126 styledHeader := emailHeaderStyle.Width(m.viewport.Width).Render(header)
127
128 var help string
129 if m.focusOnAttachments {
130 help = helpStyle.Render("↑/↓: navigate • enter: download • esc/tab: back to email body")
131 } else {
132 help = helpStyle.Render("r: reply • tab: focus attachments • esc: back to inbox")
133 }
134
135 var attachmentView string
136 if len(m.email.Attachments) > 0 {
137 var b strings.Builder
138 b.WriteString("Attachments:\n")
139 for i, attachment := range m.email.Attachments {
140 cursor := " "
141 style := itemStyle
142 if m.focusOnAttachments && i == m.attachmentCursor {
143 cursor = "> "
144 style = selectedItemStyle
145 }
146 b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, attachment.Filename)))
147 b.WriteString("\n")
148 }
149 attachmentView = attachmentBoxStyle.Render(b.String())
150 }
151
152 return fmt.Sprintf("%s\n%s\n%s\n%s", styledHeader, m.viewport.View(), attachmentView, help)
153}