1package tui
2
3import (
4 "github.com/charmbracelet/bubbles/textarea"
5 "github.com/charmbracelet/bubbles/textinput"
6 tea "github.com/charmbracelet/bubbletea"
7 "github.com/charmbracelet/lipgloss"
8)
9
10// Styles for the UI
11var (
12 focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
13 blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
14 cursorStyle = focusedStyle.Copy()
15 noStyle = lipgloss.NewStyle()
16 focusedButton = focusedStyle.Copy().Render("[ Send ]")
17 blurredButton = blurredStyle.Copy().Render("[ Send ]")
18 emailRecipientStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
19)
20
21// Composer model holds the state of the email composition UI.
22type Composer struct {
23 focusIndex int
24 toInput textinput.Model
25 subjectInput textinput.Model
26 bodyInput textarea.Model
27 fromAddr string
28}
29
30// NewComposer initializes a new composer model.
31func NewComposer(from, to, subject, body string) *Composer {
32 m := &Composer{fromAddr: from}
33
34 m.toInput = textinput.New()
35 m.toInput.Cursor.Style = cursorStyle
36 m.toInput.Placeholder = "To"
37 m.toInput.SetValue(to)
38 m.toInput.Focus()
39 m.toInput.Prompt = "> "
40 m.toInput.CharLimit = 256
41
42 m.subjectInput = textinput.New()
43 m.subjectInput.Cursor.Style = cursorStyle
44 m.subjectInput.Placeholder = "Subject"
45 m.subjectInput.SetValue(subject)
46 m.subjectInput.Prompt = "> "
47 m.subjectInput.CharLimit = 256
48
49 m.bodyInput = textarea.New()
50 m.bodyInput.Cursor.Style = cursorStyle
51 m.bodyInput.Placeholder = "Body (Markdown supported)..."
52 m.bodyInput.SetValue(body)
53 m.bodyInput.Prompt = "> "
54 m.bodyInput.SetHeight(10)
55 m.bodyInput.SetCursor(0) // Set cursor to the beginning on creation
56
57 return m
58}
59
60func (m *Composer) Init() tea.Cmd {
61 return textinput.Blink
62}
63
64func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
65 var cmds []tea.Cmd
66 var cmd tea.Cmd
67
68 switch msg := msg.(type) {
69 case tea.WindowSizeMsg:
70 // When the window is resized, update the width of the inputs.
71 inputWidth := msg.Width - 6 // Subtract for padding and prompt
72 m.toInput.Width = inputWidth
73 m.subjectInput.Width = inputWidth
74 m.bodyInput.SetWidth(inputWidth)
75
76 case SetComposerCursorToStartMsg:
77 m.bodyInput.SetCursor(0)
78 return m, nil
79
80 case tea.KeyMsg:
81 switch msg.Type {
82 case tea.KeyCtrlC:
83 return m, tea.Quit
84
85 case tea.KeyTab, tea.KeyShiftTab:
86 if msg.Type == tea.KeyShiftTab {
87 m.focusIndex--
88 } else {
89 m.focusIndex++
90 }
91
92 // Wrap around
93 if m.focusIndex > 3 { // 3 is the Send button
94 m.focusIndex = 0
95 } else if m.focusIndex < 0 {
96 m.focusIndex = 3
97 }
98
99 // Blur all inputs
100 m.toInput.Blur()
101 m.subjectInput.Blur()
102 m.bodyInput.Blur()
103
104 // Focus the correct input
105 switch m.focusIndex {
106 case 0:
107 cmds = append(cmds, m.toInput.Focus())
108 case 1:
109 cmds = append(cmds, m.subjectInput.Focus())
110 case 2:
111 cmds = append(cmds, m.bodyInput.Focus())
112 // Send a message to explicitly set the cursor position AFTER focus.
113 cmds = append(cmds, func() tea.Msg { return SetComposerCursorToStartMsg{} })
114 }
115 return m, tea.Batch(cmds...)
116
117 case tea.KeyEnter:
118 if m.focusIndex == 3 {
119 return m, func() tea.Msg {
120 return SendEmailMsg{
121 To: m.toInput.Value(),
122 Subject: m.subjectInput.Value(),
123 Body: m.bodyInput.Value(),
124 }
125 }
126 }
127 }
128 }
129
130 // Update the focused input.
131 switch m.focusIndex {
132 case 0:
133 m.toInput, cmd = m.toInput.Update(msg)
134 cmds = append(cmds, cmd)
135 case 1:
136 m.subjectInput, cmd = m.subjectInput.Update(msg)
137 cmds = append(cmds, cmd)
138 case 2:
139 m.bodyInput, cmd = m.bodyInput.Update(msg)
140 cmds = append(cmds, cmd)
141 }
142
143 return m, tea.Batch(cmds...)
144}
145
146// View renders the UI.
147func (m *Composer) View() string {
148 button := &blurredButton
149 if m.focusIndex == 3 {
150 button = &focusedButton
151 }
152
153 return lipgloss.JoinVertical(lipgloss.Left,
154 "Compose New Email",
155 "From: "+emailRecipientStyle.Render(m.fromAddr),
156 m.toInput.View(),
157 m.subjectInput.View(),
158 m.bodyInput.View(),
159 *button,
160 helpStyle.Render("Markdown/HTML • tab: next field • esc: back to menu"),
161 )
162}