9
9
tea "github.com/charmbracelet/bubbletea"
10
10
"github.com/charmbracelet/lipgloss"
11
11
//"github.com/erikgeiser/promptkit/selection"
12
+ "github.com/charmbracelet/bubbles/list"
12
13
"io"
13
14
"io/ioutil"
14
15
"net/http"
@@ -35,16 +36,14 @@ func BuildEmail() error {
35
36
//
36
37
// description: The structure for the bubbletea interface for the email sending TUI.
37
38
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.
48
47
}
49
48
50
49
// Type: errMsg
@@ -66,6 +65,19 @@ type HttpEmailMsg struct {
66
65
ReturnMsg string `json:"returnMsg"`
67
66
}
68
67
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
+
69
81
// Constants: These are the different constants for the currently focused field.
70
82
const (
71
83
accountfield = iota
@@ -94,6 +106,48 @@ var (
94
106
}
95
107
)
96
108
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
+
97
151
// Function: nameValidator
98
152
//
99
153
// Description: This is a validator functions to ensure valid input.
@@ -121,14 +175,50 @@ func stringValidator(s string) error {
121
175
// Description: This will create and initialize the model for our TUI.
122
176
func initialModel () model {
123
177
var m model
178
+ var b App
179
+ var accounts []EmailAccounts
124
180
125
181
//
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.
127
184
//
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 ("\n EmailIt 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
132
222
133
223
//
134
224
// Create the different inputs.
@@ -140,7 +230,7 @@ func initialModel() model {
140
230
m .to .Validate = nameValidator
141
231
m .to .TextStyle = inputStyle
142
232
m .to .Cursor .Style = cursorStyle
143
- m .to .Width = m . inputWidth
233
+ m .to .Width = inputWidth
144
234
if cliemail .To != "" {
145
235
m .to .SetValue (cliemail .To )
146
236
}
@@ -152,34 +242,35 @@ func initialModel() model {
152
242
m .subject .Validate = nameValidator
153
243
m .subject .TextStyle = inputStyle
154
244
m .subject .Cursor .Style = cursorStyle
155
- m .subject .Width = m . inputWidth
245
+ m .subject .Width = inputWidth
156
246
if cliemail .Subject != "" {
157
247
m .subject .SetValue (cliemail .Subject )
158
248
}
159
249
160
250
m .account = textinput .New ()
161
- m .account .Placeholder = ""
251
+ m .account .Placeholder = " "
162
252
m .account .CharLimit = 0
163
253
m .account .Prompt = ""
164
254
m .account .Validate = stringValidator
165
255
m .account .TextStyle = inputStyle
166
256
m .account .Cursor .Style = cursorStyle
167
- m .account .Width = m . inputWidth
257
+ m .account .Width = inputWidth
168
258
169
259
m .body = textarea .New ()
170
260
m .body .FocusedStyle = textareaStyle
171
261
m .body .BlurredStyle = textareaStyle
172
262
m .body .Prompt = ""
173
- m .body .SetWidth (m . textareaWidth )
263
+ m .body .SetWidth (textareaWidth )
174
264
if cliemail .Body != "" {
175
265
m .body .SetValue (cliemail .Body )
176
266
}
177
267
178
268
//
179
269
// Set up the rest of the default values.
180
270
//
181
- m .account .SetValue ("Default" )
271
+ m .account .SetValue ("Default " )
182
272
m .focused = tofield
273
+ m .sending = false
183
274
m .err = ""
184
275
return m
185
276
}
@@ -226,10 +317,12 @@ func (m model) SendMessage() tea.Msg {
226
317
//
227
318
// Create and send the email. Then quit.
228
319
//
320
+ fixacct := strings .Trim (m .account .Value (), " " )
321
+ fixto := strings .Trim (m .to .Value (), " " )
229
322
bodyJson := HttpEmailMsg {
230
- Account : m . account . Value () ,
323
+ Account : fixacct ,
231
324
From : "Default" ,
232
- To : m . to . Value () ,
325
+ To : fixto ,
233
326
Subject : m .subject .Value (),
234
327
Body : m .body .Value (),
235
328
}
@@ -262,29 +355,44 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
262
355
//
263
356
// This is the last input, save the inputs
264
357
//
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
+ }
266
370
} else if m .focused < bodyfield {
267
371
m .nextInput ()
268
- return m , nil
269
372
}
270
373
case tea .KeyCtrlC , tea .KeyEsc :
271
374
return m , tea .Quit
272
375
case tea .KeyShiftTab , tea .KeyCtrlP :
273
- m .prevInput ()
376
+ if m .focused != accountfield {
377
+ m .prevInput ()
378
+ }
274
379
case tea .KeyTab , tea .KeyCtrlN :
275
- m .nextInput ()
380
+ if m .focused != accountfield {
381
+ m .nextInput ()
382
+ }
276
383
}
277
384
385
+ //
386
+ // This case is for getting the terminal size and setting up the
387
+ // component sizes.
388
+ //
278
389
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
288
396
289
397
// We handle errors just like any other message
290
398
case errMsg :
@@ -327,9 +435,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
327
435
//
328
436
m .to , cmds [tofield ] = m .to .Update (msg )
329
437
m .subject , cmds [subjectfield ] = m .subject .Update (msg )
330
- m .account , cmds [accountfield ] = m .account .Update (msg )
331
438
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
+ }
333
444
return m , tea .Batch (cmds ... )
334
445
}
335
446
@@ -342,19 +453,26 @@ func (m model) View() string {
342
453
//
343
454
// Create the actual view string.
344
455
//
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
346
463
%s %s
347
464
%s %s
348
465
%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 ("\n Send 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 ("\n Send Email ->" )
475
+ }
358
476
}
359
477
result += "\n "
360
478
0 commit comments