email_view.go

  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}