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