feat: edit settings (#329)

Drew Smirnoff created

Change summary

main.go             | 49 +++++++++++++++++++++++++++++++++++++++++-----
tui/mailing_list.go | 37 +++++++++++++++++++++++++---------
tui/messages.go     | 23 +++++++++++++++++++++
tui/settings.go     | 35 +++++++++++++++++++++++++++++++-
4 files changed, 125 insertions(+), 19 deletions(-)

Detailed changes

main.go 🔗

@@ -187,12 +187,20 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 
 		// Check if we're editing an existing account
+		isEdit := false
 		if login, ok := m.current.(*tui.Login); ok && login.IsEditMode() {
-			// Find and update the existing account
+			isEdit = true
+			// Find and update the existing account, preserving S/MIME settings
 			existingID := login.GetAccountID()
 			for i, acc := range m.config.Accounts {
 				if acc.ID == existingID {
 					account.ID = existingID
+					account.SMIMECert = acc.SMIMECert
+					account.SMIMEKey = acc.SMIMEKey
+					account.SMIMESignByDefault = acc.SMIMESignByDefault
+					if account.Password == "" {
+						account.Password = acc.Password
+					}
 					m.config.Accounts[i] = account
 					break
 				}
@@ -206,7 +214,11 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return m, tea.Quit
 		}
 
-		m.current = tui.NewChoice()
+		if isEdit {
+			m.current = tui.NewSettings(m.config)
+		} else {
+			m.current = tui.NewChoice()
+		}
 		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
 		return m, m.current.Init()
 
@@ -556,6 +568,24 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
 		return m, m.current.Init()
 
+	case tui.GoToEditAccountMsg:
+		hideTips := false
+		if m.config != nil {
+			hideTips = m.config.HideTips
+		}
+		login := tui.NewLogin(hideTips)
+		login.SetEditMode(msg.AccountID, msg.Provider, msg.Name, msg.Email, msg.FetchEmail, msg.IMAPServer, msg.IMAPPort, msg.SMTPServer, msg.SMTPPort)
+		m.current = login
+		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
+		return m, m.current.Init()
+
+	case tui.GoToEditMailingListMsg:
+		editor := tui.NewMailingListEditor()
+		editor.SetEditMode(msg.Index, msg.Name, msg.Addresses)
+		m.current = editor
+		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
+		return m, m.current.Init()
+
 	case tui.SaveMailingListMsg:
 		if m.config != nil {
 			var addrs []string
@@ -564,10 +594,17 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 					addrs = append(addrs, trimmed)
 				}
 			}
-			m.config.MailingLists = append(m.config.MailingLists, config.MailingList{
-				Name:      msg.Name,
-				Addresses: addrs,
-			})
+			if msg.EditIndex >= 0 && msg.EditIndex < len(m.config.MailingLists) {
+				m.config.MailingLists[msg.EditIndex] = config.MailingList{
+					Name:      msg.Name,
+					Addresses: addrs,
+				}
+			} else {
+				m.config.MailingLists = append(m.config.MailingLists, config.MailingList{
+					Name:      msg.Name,
+					Addresses: addrs,
+				})
+			}
 			if err := config.SaveConfig(m.config); err != nil {
 				log.Printf("could not save config: %v", err)
 			}

tui/mailing_list.go 🔗

@@ -8,13 +8,15 @@ import (
 	"charm.land/lipgloss/v2"
 )
 
-// MailingListEditor displays the screen to add a new mailing list.
+// MailingListEditor displays the screen to add or edit a mailing list.
 type MailingListEditor struct {
-	nameInput textinput.Model
-	addrInput textinput.Model
-	focus     int // 0 = name, 1 = addresses
-	width     int
-	height    int
+	nameInput  textinput.Model
+	addrInput  textinput.Model
+	focus      int // 0 = name, 1 = addresses
+	width      int
+	height     int
+	isEditMode bool
+	editIndex  int // index of the mailing list being edited
 }
 
 // NewMailingListEditor creates a new mailing list editor model.
@@ -27,12 +29,21 @@ func NewMailingListEditor() *MailingListEditor {
 	addr.Placeholder = "e.g., alice@example.com, bob@example.com"
 
 	return &MailingListEditor{
-		nameInput: name,
-		addrInput: addr,
-		focus:     0,
+		nameInput:  name,
+		addrInput:  addr,
+		focus:      0,
+		editIndex:  -1,
 	}
 }
 
+// SetEditMode sets the editor to edit an existing mailing list.
+func (m *MailingListEditor) SetEditMode(index int, name, addresses string) {
+	m.isEditMode = true
+	m.editIndex = index
+	m.nameInput.SetValue(name)
+	m.addrInput.SetValue(addresses)
+}
+
 // Init initializes the mailing list editor model.
 func (m *MailingListEditor) Init() tea.Cmd {
 	return textinput.Blink
@@ -78,10 +89,12 @@ func (m *MailingListEditor) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 				name := strings.TrimSpace(m.nameInput.Value())
 				addrs := strings.TrimSpace(m.addrInput.Value())
 				if name != "" && addrs != "" {
+					editIdx := m.editIndex
 					return m, func() tea.Msg {
 						return SaveMailingListMsg{
 							Name:      name,
 							Addresses: addrs,
+							EditIndex: editIdx,
 						}
 					}
 				}
@@ -100,7 +113,11 @@ func (m *MailingListEditor) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 // View renders the mailing list editor screen.
 func (m *MailingListEditor) View() tea.View {
-	title := titleStyle.Render("Add Mailing List")
+	titleText := "Add Mailing List"
+	if m.isEditMode {
+		titleText = "Edit Mailing List"
+	}
+	title := titleStyle.Render(titleText)
 
 	var nameView, addrView string
 	if m.focus == 0 {

tui/messages.go 🔗

@@ -184,10 +184,31 @@ type GoToAddAccountMsg struct{}
 // GoToAddMailingListMsg signals navigation to the add mailing list screen.
 type GoToAddMailingListMsg struct{}
 
-// SaveMailingListMsg signals that a new mailing list should be saved.
+// GoToEditAccountMsg signals navigation to edit an existing account.
+type GoToEditAccountMsg struct {
+	AccountID      string
+	Provider       string
+	Name           string
+	Email          string
+	FetchEmail     string
+	IMAPServer     string
+	IMAPPort       int
+	SMTPServer     string
+	SMTPPort       int
+}
+
+// GoToEditMailingListMsg signals navigation to edit an existing mailing list.
+type GoToEditMailingListMsg struct {
+	Index     int
+	Name      string
+	Addresses string
+}
+
+// SaveMailingListMsg signals that a new or edited mailing list should be saved.
 type SaveMailingListMsg struct {
 	Name      string
 	Addresses string
+	EditIndex int // -1 means new, >= 0 means editing existing
 }
 
 // AddAccountMsg signals that a new account should be added.

tui/settings.go 🔗

@@ -200,6 +200,24 @@ func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 		if m.cursor < len(m.cfg.Accounts) && len(m.cfg.Accounts) > 0 {
 			m.confirmingDelete = true
 		}
+	case "e":
+		// Edit selected account
+		if m.cursor < len(m.cfg.Accounts) {
+			acc := m.cfg.Accounts[m.cursor]
+			return m, func() tea.Msg {
+				return GoToEditAccountMsg{
+					AccountID:  acc.ID,
+					Provider:   acc.ServiceProvider,
+					Name:       acc.Name,
+					Email:      acc.Email,
+					FetchEmail: acc.FetchEmail,
+					IMAPServer: acc.IMAPServer,
+					IMAPPort:   acc.IMAPPort,
+					SMTPServer: acc.SMTPServer,
+					SMTPPort:   acc.SMTPPort,
+				}
+			}
+		}
 	case "enter":
 		// If cursor is on "Add Account"
 		if m.cursor == len(m.cfg.Accounts) {
@@ -322,6 +340,19 @@ func (m *Settings) updateMailingLists(msg tea.KeyPressMsg) (tea.Model, tea.Cmd)
 		if m.cursor < len(m.cfg.MailingLists) && len(m.cfg.MailingLists) > 0 {
 			m.confirmingDelete = true
 		}
+	case "e":
+		// Edit selected mailing list
+		if m.cursor < len(m.cfg.MailingLists) {
+			list := m.cfg.MailingLists[m.cursor]
+			idx := m.cursor
+			return m, func() tea.Msg {
+				return GoToEditMailingListMsg{
+					Index:     idx,
+					Name:      list.Name,
+					Addresses: strings.Join(list.Addresses, ", "),
+				}
+			}
+		}
 	case "enter":
 		if m.cursor == len(m.cfg.MailingLists) {
 			return m, func() tea.Msg { return GoToAddMailingListMsg{} }
@@ -500,7 +531,7 @@ func (m *Settings) viewAccounts() string {
 	b.WriteString("\n")
 
 	mainContent := b.String()
-	helpView := helpStyle.Render("↑/↓: navigate • enter: select • d: delete account • esc: back")
+	helpView := helpStyle.Render("↑/↓: navigate • enter: select • e: edit • d: delete • esc: back")
 
 	if m.height > 0 {
 		currentHeight := lipgloss.Height(docStyle.Render(mainContent + helpView))
@@ -629,7 +660,7 @@ func (m *Settings) viewMailingLists() string {
 	}
 	b.WriteString("\n")
 
-	helpView := helpStyle.Render("↑/↓: navigate • enter: select • d: delete • esc: back")
+	helpView := helpStyle.Render("↑/↓: navigate • enter: select • e: edit • d: delete • esc: back")
 	mainContent := b.String()
 
 	if m.height > 0 {