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}
 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}