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 accountID string
26}
27
28func NewEmailView(email fetcher.Email, emailIndex, width, height int) *EmailView {
29 // Pass the styles from the tui package to the view package
30 body, err := view.ProcessBody(email.Body, H1Style, H2Style, BodyStyle)
31 if err != nil {
32 body = fmt.Sprintf("Error rendering body: %v", err)
33 }
34
35 header := fmt.Sprintf("From: %s\nSubject: %s", email.From, email.Subject)
36 headerHeight := lipgloss.Height(header) + 2
37
38 attachmentHeight := 0
39 if len(email.Attachments) > 0 {
40 attachmentHeight = len(email.Attachments) + 2
41 }
42
43 vp := viewport.New(width, height-headerHeight-attachmentHeight)
44 vp.SetContent(body)
45
46 return &EmailView{
47 viewport: vp,
48 email: email,
49 emailIndex: emailIndex,
50 accountID: email.AccountID,
51 }
52}
53
54func (m *EmailView) Init() tea.Cmd {
55 return nil
56}
57
58func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
59 var cmd tea.Cmd
60 var cmds []tea.Cmd
61
62 switch msg := msg.(type) {
63 case tea.KeyMsg:
64 // Handle 'esc' key locally
65 if msg.Type == tea.KeyEsc {
66 if m.focusOnAttachments {
67 m.focusOnAttachments = false
68 return m, nil
69 }
70 return m, func() tea.Msg { return BackToInboxMsg{} }
71 }
72
73 if m.focusOnAttachments {
74 switch msg.String() {
75 case "up", "k":
76 if m.attachmentCursor > 0 {
77 m.attachmentCursor--
78 }
79 case "down", "j":
80 if m.attachmentCursor < len(m.email.Attachments)-1 {
81 m.attachmentCursor++
82 }
83 case "enter":
84 if len(m.email.Attachments) > 0 {
85 selected := m.email.Attachments[m.attachmentCursor]
86 idx := m.emailIndex
87 accountID := m.accountID
88 return m, func() tea.Msg {
89 return DownloadAttachmentMsg{
90 Index: idx,
91 Filename: selected.Filename,
92 PartID: selected.PartID,
93 Data: selected.Data,
94 AccountID: accountID,
95 }
96 }
97 }
98 case "tab":
99 m.focusOnAttachments = false
100 }
101 } else {
102 switch msg.String() {
103 case "r":
104 return m, func() tea.Msg { return ReplyToEmailMsg{Email: m.email} }
105 case "d":
106 accountID := m.accountID
107 uid := m.email.UID
108 return m, func() tea.Msg {
109 return DeleteEmailMsg{UID: uid, AccountID: accountID}
110 }
111 case "a":
112 accountID := m.accountID
113 uid := m.email.UID
114 return m, func() tea.Msg {
115 return ArchiveEmailMsg{UID: uid, AccountID: accountID}
116 }
117 case "tab":
118 if len(m.email.Attachments) > 0 {
119 m.focusOnAttachments = true
120 }
121 }
122 }
123 case tea.WindowSizeMsg:
124 header := fmt.Sprintf("From: %s\nSubject: %s", m.email.From, m.email.Subject)
125 headerHeight := lipgloss.Height(header) + 2
126 attachmentHeight := 0
127 if len(m.email.Attachments) > 0 {
128 attachmentHeight = len(m.email.Attachments) + 2
129 }
130 m.viewport.Width = msg.Width
131 m.viewport.Height = msg.Height - headerHeight - attachmentHeight
132 }
133
134 m.viewport, cmd = m.viewport.Update(msg)
135 cmds = append(cmds, cmd)
136
137 return m, tea.Batch(cmds...)
138}
139
140func (m *EmailView) View() string {
141 header := fmt.Sprintf("From: %s | Subject: %s", m.email.From, m.email.Subject)
142 styledHeader := emailHeaderStyle.Width(m.viewport.Width).Render(header)
143
144 var help string
145 if m.focusOnAttachments {
146 help = helpStyle.Render("↑/↓: navigate • enter: download • esc/tab: back to email body")
147 } else {
148 help = helpStyle.Render("r: reply • d: delete • a: archive • tab: focus attachments • esc: back to inbox")
149 }
150
151 var attachmentView string
152 if len(m.email.Attachments) > 0 {
153 var b strings.Builder
154 b.WriteString("Attachments:\n")
155 for i, attachment := range m.email.Attachments {
156 cursor := " "
157 style := itemStyle
158 if m.focusOnAttachments && i == m.attachmentCursor {
159 cursor = "> "
160 style = selectedItemStyle
161 }
162 b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, attachment.Filename)))
163 b.WriteString("\n")
164 }
165 attachmentView = attachmentBoxStyle.Render(b.String())
166 }
167
168 return fmt.Sprintf("%s\n%s\n%s\n%s", styledHeader, m.viewport.View(), attachmentView, help)
169}
170
171// GetAccountID returns the account ID for this email
172func (m *EmailView) GetAccountID() string {
173 return m.accountID
174}
175
176// GetEmail returns the email being viewed
177func (m *EmailView) GetEmail() fetcher.Email {
178 return m.email
179}