Skip to content

Commit 4c3b1b5

Browse files
committed
Upgraded libraries and fix TUI for selecting the right account and not sending the email more than once.
1 parent a053a40 commit 4c3b1b5

File tree

7 files changed

+307
-177
lines changed

7 files changed

+307
-177
lines changed

.todo.db

0 Bytes
Binary file not shown.

buildcli.go

+166-48
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
tea "github.com/charmbracelet/bubbletea"
1010
"github.com/charmbracelet/lipgloss"
1111
//"github.com/erikgeiser/promptkit/selection"
12+
"github.com/charmbracelet/bubbles/list"
1213
"io"
1314
"io/ioutil"
1415
"net/http"
@@ -35,16 +36,14 @@ func BuildEmail() error {
3536
//
3637
// description: The structure for the bubbletea interface for the email sending TUI.
3738
type model struct {
38-
account textinput.Model // This is for the account.
39-
to textinput.Model // this is for the to address
40-
subject textinput.Model // this is the subject line
41-
body textarea.Model // The body of the message.
42-
focused int // This is the currently focused input
43-
err string // this will contain any errors from the validators
44-
inputWidth int // Text Input width
45-
textareaWidth int // Text Area width
46-
screenHeight int // The height of the terminal.
47-
screenWidth int // the width of the terminal
39+
account textinput.Model // This is for the account.
40+
to textinput.Model // this is for the to address
41+
subject textinput.Model // this is the subject line
42+
body textarea.Model // The body of the message.
43+
focused int // This is the currently focused input
44+
err string // this will contain any errors from the validators
45+
list list.Model // list for choosing accounts
46+
sending bool // flag if sending or not.
4847
}
4948

5049
// Type: errMsg
@@ -66,6 +65,19 @@ type HttpEmailMsg struct {
6665
ReturnMsg string `json:"returnMsg"`
6766
}
6867

68+
type EmailAccounts struct {
69+
Default bool `json:"default" binding:"required"`
70+
FooterHTML string `json:"footerHTML" binding:"required"`
71+
From string `json:"from" binding:"required"`
72+
HeaderHTML string `json:"headerHTML" binding:"required"`
73+
Name string `json:"name" binding:"required"`
74+
Password string `json:"passwd" binding:"required"`
75+
Port string `json:"port" binding:"required"`
76+
Signiture string `json:"signiture" binding:"required"`
77+
SmtpServer string `json:"smtpserver" binding:"required"`
78+
Username string `json:"usersname" binding:"required"`
79+
}
80+
6981
// Constants: These are the different constants for the currently focused field.
7082
const (
7183
accountfield = iota
@@ -94,6 +106,48 @@ var (
94106
}
95107
)
96108

109+
// These items are for the accounts list.
110+
111+
var (
112+
titleStyle = lipgloss.NewStyle().MarginLeft(2).Foreground(lipgloss.Color("#9580FF"))
113+
itemStyle = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("#80FFEA"))
114+
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("#9F70A9"))
115+
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
116+
helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
117+
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
118+
)
119+
120+
type item string
121+
122+
func (i item) FilterValue() string { return "" }
123+
124+
type itemDelegate struct{}
125+
126+
func (d itemDelegate) Height() int { return 1 }
127+
func (d itemDelegate) Spacing() int { return 0 }
128+
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
129+
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
130+
i, ok := listItem.(item)
131+
if !ok {
132+
return
133+
}
134+
135+
str := fmt.Sprintf("%d. %s", index+1, i)
136+
137+
fn := itemStyle.Render
138+
if index == m.Index() {
139+
fn = func(s ...string) string {
140+
return selectedItemStyle.Render("> " + strings.Join(s, " "))
141+
}
142+
}
143+
144+
fmt.Fprint(w, fn(str))
145+
}
146+
147+
//
148+
// End of list items
149+
//
150+
97151
// Function: nameValidator
98152
//
99153
// Description: This is a validator functions to ensure valid input.
@@ -121,14 +175,50 @@ func stringValidator(s string) error {
121175
// Description: This will create and initialize the model for our TUI.
122176
func initialModel() model {
123177
var m model
178+
var b App
179+
var accounts []EmailAccounts
124180

125181
//
126-
// Set the different widths. Should be based on terminal size.
182+
// Set the different widths. Should be based on terminal size and will
183+
// be replaced when the terminal size message is sent.
127184
//
128-
m.inputWidth = 92
129-
m.textareaWidth = 100
130-
m.screenHeight = 100
131-
m.screenWidth = 100
185+
inputWidth := 92
186+
textareaWidth := 100
187+
188+
//
189+
// Get the list of accounts.
190+
//
191+
hmdir := b.GetHomeDir()
192+
accountsPath := b.AppendPath(hmdir, ".config/EmailIt/emailaccounts.json")
193+
items := []list.Item{}
194+
numacc := 0
195+
if b.FileExists(accountsPath) {
196+
accountsText := b.ReadFile(accountsPath)
197+
err := json.Unmarshal([]byte(accountsText), &accounts)
198+
if err != nil {
199+
fmt.Printf("Couldn't parse the JSON accounts file! %s", err.Error())
200+
os.Exit(0)
201+
} else {
202+
numacc = len(accounts) + 1
203+
items = make([]list.Item, numacc)
204+
items[0] = item("Default")
205+
for i := 1; i < numacc; i++ {
206+
items[i] = item(accounts[i-1].Name)
207+
}
208+
}
209+
} else {
210+
fmt.Print("\nEmailIt doesn't have accounts setup. Please setup accounts before using the TUI.\n")
211+
os.Exit(0)
212+
}
213+
214+
l := list.New(items, itemDelegate{}, 50, numacc+8)
215+
l.Title = "Which account to you want to use?"
216+
l.SetShowStatusBar(false)
217+
l.SetFilteringEnabled(false)
218+
l.Styles.Title = titleStyle
219+
l.Styles.PaginationStyle = paginationStyle
220+
l.Styles.HelpStyle = helpStyle
221+
m.list = l
132222

133223
//
134224
// Create the different inputs.
@@ -140,7 +230,7 @@ func initialModel() model {
140230
m.to.Validate = nameValidator
141231
m.to.TextStyle = inputStyle
142232
m.to.Cursor.Style = cursorStyle
143-
m.to.Width = m.inputWidth
233+
m.to.Width = inputWidth
144234
if cliemail.To != "" {
145235
m.to.SetValue(cliemail.To)
146236
}
@@ -152,34 +242,35 @@ func initialModel() model {
152242
m.subject.Validate = nameValidator
153243
m.subject.TextStyle = inputStyle
154244
m.subject.Cursor.Style = cursorStyle
155-
m.subject.Width = m.inputWidth
245+
m.subject.Width = inputWidth
156246
if cliemail.Subject != "" {
157247
m.subject.SetValue(cliemail.Subject)
158248
}
159249

160250
m.account = textinput.New()
161-
m.account.Placeholder = ""
251+
m.account.Placeholder = " "
162252
m.account.CharLimit = 0
163253
m.account.Prompt = ""
164254
m.account.Validate = stringValidator
165255
m.account.TextStyle = inputStyle
166256
m.account.Cursor.Style = cursorStyle
167-
m.account.Width = m.inputWidth
257+
m.account.Width = inputWidth
168258

169259
m.body = textarea.New()
170260
m.body.FocusedStyle = textareaStyle
171261
m.body.BlurredStyle = textareaStyle
172262
m.body.Prompt = ""
173-
m.body.SetWidth(m.textareaWidth)
263+
m.body.SetWidth(textareaWidth)
174264
if cliemail.Body != "" {
175265
m.body.SetValue(cliemail.Body)
176266
}
177267

178268
//
179269
// Set up the rest of the default values.
180270
//
181-
m.account.SetValue("Default")
271+
m.account.SetValue("Default ")
182272
m.focused = tofield
273+
m.sending = false
183274
m.err = ""
184275
return m
185276
}
@@ -226,10 +317,12 @@ func (m model) SendMessage() tea.Msg {
226317
//
227318
// Create and send the email. Then quit.
228319
//
320+
fixacct := strings.Trim(m.account.Value(), " ")
321+
fixto := strings.Trim(m.to.Value(), " ")
229322
bodyJson := HttpEmailMsg{
230-
Account: m.account.Value(),
323+
Account: fixacct,
231324
From: "Default",
232-
To: m.to.Value(),
325+
To: fixto,
233326
Subject: m.subject.Value(),
234327
Body: m.body.Value(),
235328
}
@@ -262,29 +355,44 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
262355
//
263356
// This is the last input, save the inputs
264357
//
265-
return m, m.SendMessage
358+
if !m.sending {
359+
m.sending = true
360+
return m, m.SendMessage
361+
} else {
362+
return m, nil
363+
}
364+
} else if m.focused == accountfield {
365+
i, ok := m.list.SelectedItem().(item)
366+
if ok {
367+
m.account.SetValue(string(i))
368+
m.nextInput()
369+
}
266370
} else if m.focused < bodyfield {
267371
m.nextInput()
268-
return m, nil
269372
}
270373
case tea.KeyCtrlC, tea.KeyEsc:
271374
return m, tea.Quit
272375
case tea.KeyShiftTab, tea.KeyCtrlP:
273-
m.prevInput()
376+
if m.focused != accountfield {
377+
m.prevInput()
378+
}
274379
case tea.KeyTab, tea.KeyCtrlN:
275-
m.nextInput()
380+
if m.focused != accountfield {
381+
m.nextInput()
382+
}
276383
}
277384

385+
//
386+
// This case is for getting the terminal size and setting up the
387+
// component sizes.
388+
//
278389
case tea.WindowSizeMsg:
279-
m.screenWidth = msg.Width
280-
m.screenHeight = msg.Height
281-
m.textareaWidth = m.screenWidth - 10
282-
m.inputWidth = m.screenWidth - 18
283-
m.to.Width = m.inputWidth
284-
m.account.Width = m.inputWidth
285-
m.body.SetWidth(m.textareaWidth)
286-
m.body.SetHeight(m.screenHeight - 7)
287-
return m, nil
390+
m.to.Width = msg.Width - 18
391+
m.account.Width = msg.Width - 18
392+
m.body.SetWidth(msg.Width - 10)
393+
m.body.SetHeight(msg.Height - 7)
394+
m.list.SetWidth(msg.Width - 10)
395+
//return m, nil
288396

289397
// We handle errors just like any other message
290398
case errMsg:
@@ -327,9 +435,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
327435
//
328436
m.to, cmds[tofield] = m.to.Update(msg)
329437
m.subject, cmds[subjectfield] = m.subject.Update(msg)
330-
m.account, cmds[accountfield] = m.account.Update(msg)
331438
m.body, cmds[bodyfield] = m.body.Update(msg)
332-
439+
if m.focused == accountfield {
440+
m.list, cmds[accountfield] = m.list.Update(msg)
441+
} else {
442+
m.account, cmds[accountfield] = m.account.Update(msg)
443+
}
333444
return m, tea.Batch(cmds...)
334445
}
335446

@@ -342,19 +453,26 @@ func (m model) View() string {
342453
//
343454
// Create the actual view string.
344455
//
345-
result := fmt.Sprintf(`%s %s
456+
result := ""
457+
if m.focused == accountfield {
458+
result = m.list.View()
459+
} else if m.sending {
460+
result = labelStyle.Width(25).Render("The Email is sending....")
461+
} else {
462+
result = fmt.Sprintf(`%s %s
346463
%s %s
347464
%s %s
348465
%s`,
349-
labelStyle.Width(7).Render("Account"),
350-
m.account.View(),
351-
labelStyle.Width(7).Render(" To"),
352-
m.to.View(),
353-
labelStyle.Width(7).Render("Subject"),
354-
m.subject.View(),
355-
m.body.View())
356-
if m.focused == sendfield {
357-
result += continueStyle.Render("\nSend Email ->")
466+
labelStyle.Width(7).Render("Account"),
467+
m.account.View(),
468+
labelStyle.Width(7).Render(" To"),
469+
m.to.View(),
470+
labelStyle.Width(7).Render("Subject"),
471+
m.subject.View(),
472+
m.body.View())
473+
if m.focused == sendfield {
474+
result += continueStyle.Render("\nSend Email ->")
475+
}
358476
}
359477
result += "\n"
360478

0 commit comments

Comments
 (0)