From fd781ce75efeef644d2ed6b69d71f04da84ab946 Mon Sep 17 00:00:00 2001 From: Daniel Purnomo Date: Thu, 23 Apr 2026 13:15:10 +0200 Subject: [PATCH] feat: per-account email signatures (#847) Co-authored-by: drew --- config/config.go | 3 + config/signature.go | 79 +++++++++++++++++++-- i18n/locales/ar.json | 2 +- i18n/locales/de.json | 2 +- i18n/locales/en.json | 2 +- i18n/locales/es.json | 2 +- i18n/locales/fr.json | 2 +- i18n/locales/ja.json | 2 +- i18n/locales/pl.json | 2 +- i18n/locales/pt.json | 2 +- i18n/locales/ru.json | 2 +- i18n/locales/uk.json | 2 +- i18n/locales/zh.json | 2 +- main.go | 2 +- tui/composer.go | 26 +++++-- tui/messages.go | 4 +- tui/settings_accounts.go | 8 +++ tui/settings_general.go | 148 +++++++++++++++++++++++++-------------- tui/signature.go | 28 +++++--- 19 files changed, 237 insertions(+), 83 deletions(-) diff --git a/config/config.go b/config/config.go index 7b3720fce59dbb989c7afcaad5f469402043966b..a1edb4d6ee7b1e48353f9b2194fc78fe0266e4a8 100644 --- a/config/config.go +++ b/config/config.go @@ -70,6 +70,9 @@ type Account struct { JMAPEndpoint string `json:"jmap_endpoint,omitempty"` // JMAP session URL (for protocol=jmap) POP3Server string `json:"pop3_server,omitempty"` // POP3 server hostname (for protocol=pop3) POP3Port int `json:"pop3_port,omitempty"` // POP3 server port (for protocol=pop3) + + // Per-account signature (overrides global signature) + Signature string `json:"signature,omitempty"` } // MailingList represents a named group of email addresses. diff --git a/config/signature.go b/config/signature.go index e2e8e0d72b0c8e7c1d8ef5a52776f95831ed1d9c..2bf8486df6ba5ab916d93c0c8bc58f15f055bd3e 100644 --- a/config/signature.go +++ b/config/signature.go @@ -5,7 +5,7 @@ import ( "path/filepath" ) -// signatureFile returns the full path to the signature file. +// signatureFile returns the full path to the global signature file. func signatureFile() (string, error) { dir, err := configDir() if err != nil { @@ -14,7 +14,16 @@ func signatureFile() (string, error) { return filepath.Join(dir, "signature.txt"), nil } -// LoadSignature loads the signature from the signature file. +// accountSignatureFile returns the path to the per-account signature file. +func accountSignatureFile(accountID string) (string, error) { + dir, err := configDir() + if err != nil { + return "", err + } + return filepath.Join(dir, "signatures", accountID+".txt"), nil +} + +// LoadSignature loads the signature from the global signature file. func LoadSignature() (string, error) { path, err := signatureFile() if err != nil { @@ -30,7 +39,43 @@ func LoadSignature() (string, error) { return string(data), nil } -// SaveSignature saves the signature to the signature file. +// LoadRawAccountSignature loads the per-account signature if one exists, +// without falling back to the global signature. +func LoadRawAccountSignature(account *Account) (string, error) { + if account == nil || account.ID == "" { + return "", nil + } + + // Check for per-account signature file first + path, err := accountSignatureFile(account.ID) + if err != nil { + return "", err + } + data, err := SecureReadFile(path) + if err == nil && len(data) > 0 { + return string(data), nil + } + + // Fall back to inline account signature + if account.Signature != "" { + return account.Signature, nil + } + + return "", nil +} + +// LoadSignatureForAccount loads the per-account signature if one exists, +// otherwise falls back to the global signature. +func LoadSignatureForAccount(account *Account) (string, error) { + sig, err := LoadRawAccountSignature(account) + if err == nil && sig != "" { + return sig, nil + } + // Fall back to global signature + return LoadSignature() +} + +// SaveSignature saves the signature to the global signature file. func SaveSignature(signature string) error { path, err := signatureFile() if err != nil { @@ -42,7 +87,24 @@ func SaveSignature(signature string) error { return SecureWriteFile(path, []byte(signature), 0600) } -// HasSignature checks if a signature file exists and is non-empty. +// SaveSignatureForAccount saves a per-account signature file. +func SaveSignatureForAccount(accountID, signature string) error { + path, err := accountSignatureFile(accountID) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + if signature == "" { + // Remove the file to fall back to global + os.Remove(path) + return nil + } + return SecureWriteFile(path, []byte(signature), 0600) +} + +// HasSignature checks if a global signature file exists and is non-empty. func HasSignature() bool { sig, err := LoadSignature() if err != nil { @@ -50,3 +112,12 @@ func HasSignature() bool { } return sig != "" } + +// HasAccountSignature checks if an account has its own signature (file or inline). +func HasAccountSignature(account *Account) bool { + sig, err := LoadRawAccountSignature(account) + if err != nil { + return false + } + return sig != "" +} diff --git a/i18n/locales/ar.json b/i18n/locales/ar.json index a22572a02c4c1db0256bbb97c5817ad59f6dab2c..656f138490677e10e136ec397632e8a55946a67e 100644 --- a/i18n/locales/ar.json +++ b/i18n/locales/ar.json @@ -123,7 +123,7 @@ "title": "إعدادات الحسابات", "no_accounts": "لم يتم تكوين حسابات.", "add_account": "إضافة حساب جديد", - "help": "↑/↓: التنقل • enter: تعديل إعدادات التشفير • e: تعديل الخادم • d: حذف" + "help": "↑/↓: التنقل • enter: تعديل إعدادات التشفير • e: تعديل الخادم • s: تعديل التوقيع • d: حذف" }, "settings_theme": { "title": "المظهر", diff --git a/i18n/locales/de.json b/i18n/locales/de.json index d2e7a10d76c754cbc2d71e831b5fe3d3217caa54..6333d2b0ffdc80f24fc165e31c765fa3f487da25 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -121,7 +121,7 @@ "title": "Kontoeinstellungen", "no_accounts": "Keine Konten konfiguriert.", "add_account": "Neues Konto Hinzufügen", - "help": "↑/↓: navigieren • enter: Krypto-Konfig. bearbeiten • e: Server bearbeiten • d: löschen" + "help": "↑/↓: navigieren • enter: Krypto-Konfig. bearbeiten • e: Server bearbeiten • s: Signatur bearbeiten • d: löschen" }, "settings_theme": { "title": "Design", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 92f520f05456314c032513b3434271523ac95481..e05984f4d6231585ae5c25332df39dfa375fae02 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -121,7 +121,7 @@ "title": "Account Settings", "no_accounts": "No accounts configured.", "add_account": "Add New Account", - "help": "↑/↓: navigate • enter: edit crypto config • e: edit server • d: delete" + "help": "↑/↓: navigate • enter: edit crypto config • e: edit server • s: edit signature • d: delete" }, "settings_theme": { "title": "Theme", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index 537b973b6a28e9059217917651c1dec92bac6b57..0dbb7962f6093f64495cad1228b2455b093a1388 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -121,7 +121,7 @@ "title": "Configuración de Cuentas", "no_accounts": "No hay cuentas configuradas.", "add_account": "Agregar Nueva Cuenta", - "help": "↑/↓: navegar • enter: editar config. de cifrado • e: editar servidor • d: eliminar" + "help": "↑/↓: navegar • enter: editar config. de cifrado • e: editar servidor • s: editar firma • d: eliminar" }, "settings_theme": { "title": "Tema", diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json index d4436bc8b222f933492a893f6ce09adef0c63389..69d55f11f2eaec750d495a6fd9414838d45c51d1 100644 --- a/i18n/locales/fr.json +++ b/i18n/locales/fr.json @@ -121,7 +121,7 @@ "title": "Paramètres des Comptes", "no_accounts": "Aucun compte configuré.", "add_account": "Ajouter un Nouveau Compte", - "help": "↑/↓: naviguer • entrée: modifier config. crypto • e: modifier serveur • d: supprimer" + "help": "↑/↓: naviguer • entrée: modifier config. crypto • e: modifier serveur • s: modifier signature • d: supprimer" }, "settings_theme": { "title": "Thème", diff --git a/i18n/locales/ja.json b/i18n/locales/ja.json index 0efda533237b284fd56368240436800381f176b3..43914e7240a6d29484311f318655dc78b9ae8801 100644 --- a/i18n/locales/ja.json +++ b/i18n/locales/ja.json @@ -120,7 +120,7 @@ "title": "アカウント設定", "no_accounts": "アカウントが設定されていません。", "add_account": "新しいアカウントを追加", - "help": "↑/↓: 移動 • enter: 暗号化設定を編集 • e: サーバーを編集 • d: 削除" + "help": "↑/↓: 移動 • enter: 暗号化設定を編集 • e: サーバーを編集 • s: 署名を編集 • d: 削除" }, "settings_theme": { "title": "テーマ", diff --git a/i18n/locales/pl.json b/i18n/locales/pl.json index 3ffa97c751fe03948cc28ea80ab6dffe75e4013e..e480e6ae9f212d1a5b44b9034ee59793657f71f0 100644 --- a/i18n/locales/pl.json +++ b/i18n/locales/pl.json @@ -123,7 +123,7 @@ "title": "Ustawienia Kont", "no_accounts": "Brak skonfigurowanych kont.", "add_account": "Dodaj Nowe Konto", - "help": "↑/↓: nawigacja • enter: edytuj konfigurację szyfrowania • e: edytuj serwer • d: usuń" + "help": "↑/↓: nawigacja • enter: edytuj konfigurację szyfrowania • e: edytuj serwer • s: edytuj podpis • d: usuń" }, "settings_theme": { "title": "Motyw", diff --git a/i18n/locales/pt.json b/i18n/locales/pt.json index f8cec1f42eb73bd0cd1b695fdd24591d1dec56c5..577df47c4eac33c44381045d9fc4ccddfe19290e 100644 --- a/i18n/locales/pt.json +++ b/i18n/locales/pt.json @@ -121,7 +121,7 @@ "title": "Configurações de Contas", "no_accounts": "Nenhuma conta configurada.", "add_account": "Adicionar Nova Conta", - "help": "↑/↓: navegar • enter: editar config. de criptografia • e: editar servidor • d: excluir" + "help": "↑/↓: navegar • enter: editar config. de criptografia • e: editar servidor • s: editar assinatura • d: excluir" }, "settings_theme": { "title": "Tema", diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index c4cf084947f7950fa9b63dc8045215680cfd08c1..4d8b3e62fd70c12c9dc4142b0502762038d70580 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -123,7 +123,7 @@ "title": "Настройки Учётных Записей", "no_accounts": "Учётные записи не настроены.", "add_account": "Добавить Новую Учётную Запись", - "help": "↑/↓: навигация • enter: редактировать конфигурацию шифрования • e: редактировать сервер • d: удалить" + "help": "↑/↓: навигация • enter: редактировать конфигурацию шифрования • e: редактировать сервер • s: редактировать подпись • d: удалить" }, "settings_theme": { "title": "Тема", diff --git a/i18n/locales/uk.json b/i18n/locales/uk.json index bfdc3d2d9487ecf33e2527391a49947e08322691..9879b9569a9b16df435d92a48883793b1c63a8e7 100644 --- a/i18n/locales/uk.json +++ b/i18n/locales/uk.json @@ -122,7 +122,7 @@ "title": "Налаштування облікових записів", "no_accounts": "Облікові записи не налаштовано.", "add_account": "Додати новий обліковий запис", - "help": "↑/↓: навігація • enter: редагувати криптоконфіг • e: редагувати сервер • d: видалити" + "help": "↑/↓: навігація • enter: редагувати криптоконфіг • e: редагувати сервер • s: редагувати підпис • d: видалити" }, "settings_theme": { "title": "Тема", diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index 99e01fede3df0128b82a779165a0637d5aa1a98e..17ac5c6a26e9ccd1aec9536c1ebb881eb8828dad 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -120,7 +120,7 @@ "title": "账户设置", "no_accounts": "未配置账户。", "add_account": "添加新账户", - "help": "↑/↓: 导航 • enter: 编辑加密配置 • e: 编辑服务器 • d: 删除" + "help": "↑/↓: 导航 • enter: 编辑加密配置 • e: 编辑服务器 • s: 编辑签名 • d: 删除" }, "settings_theme": { "title": "主题", diff --git a/main.go b/main.go index cbd123339c0dd1090fd7c8ff294434adb413d8d2..6fb3f667d7723530bce951e5ecbdfd94362cada7 100644 --- a/main.go +++ b/main.go @@ -961,7 +961,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, m.current.Init() case tui.GoToSignatureEditorMsg: - m.current = tui.NewSignatureEditor() + m.current = tui.NewSignatureEditor(msg.AccountID) m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height}) return m, m.current.Init() diff --git a/tui/composer.go b/tui/composer.go index 27f75bc95d76bf5adff065da7806f578db596d52..c0d22d07228450bea8d565c0e8226d94f2fcdd4c 100644 --- a/tui/composer.go +++ b/tui/composer.go @@ -139,10 +139,7 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer { m.signatureInput.Prompt = "> " m.signatureInput.SetHeight(3) m.signatureInput.SetStyles(taStyles) - // Load default signature - if sig, err := config.LoadSignature(); err == nil && sig != "" { - m.signatureInput.SetValue(sig) - } + m.updateSignature() // Start focus on To field (From is selectable but not a text input) m.focusIndex = focusTo @@ -151,6 +148,22 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer { return m } +// updateSignature updates the signature input based on the current selected account. +func (m *Composer) updateSignature() { + if len(m.accounts) > 0 && m.selectedAccountIdx < len(m.accounts) { + if sig, err := config.LoadSignatureForAccount(&m.accounts[m.selectedAccountIdx]); err == nil && sig != "" { + m.signatureInput.SetValue(sig) + return + } + } + + if sig, err := config.LoadSignature(); err == nil && sig != "" { + m.signatureInput.SetValue(sig) + } else { + m.signatureInput.SetValue("") + } +} + // NewComposerWithAccounts initializes a composer with multiple account support. func NewComposerWithAccounts(accounts []config.Account, selectedAccountID string, to, subject, body string, hideTips bool) *Composer { m := NewComposer("", to, subject, body, hideTips) @@ -163,6 +176,7 @@ func NewComposerWithAccounts(accounts []config.Account, selectedAccountID string break } } + m.updateSignature() return m } @@ -319,10 +333,12 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "up", "k": if m.selectedAccountIdx > 0 { m.selectedAccountIdx-- + m.updateSignature() } case "down", "j": if m.selectedAccountIdx < len(m.accounts)-1 { m.selectedAccountIdx++ + m.updateSignature() } case "enter": m.showAccountPicker = false @@ -698,6 +714,7 @@ func (m *Composer) SetAccounts(accounts []config.Account) { if m.selectedAccountIdx >= len(accounts) { m.selectedAccountIdx = 0 } + m.updateSignature() } // SetSelectedAccount sets the selected account by ID. @@ -705,6 +722,7 @@ func (m *Composer) SetSelectedAccount(accountID string) { for i, acc := range m.accounts { if acc.ID == accountID { m.selectedAccountIdx = i + m.updateSignature() return } } diff --git a/tui/messages.go b/tui/messages.go index 7f0a18753d174199fcb3ff37739cd96f171e1f87..285706abad16a98885bf26e7bbb94f25a45c445e 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -102,7 +102,9 @@ type GoToSettingsMsg struct{} type GoToTrashArchiveMsg struct{} -type GoToSignatureEditorMsg struct{} +type GoToSignatureEditorMsg struct { + AccountID string +} type FetchMoreEmailsMsg struct { Offset uint32 diff --git a/tui/settings_accounts.go b/tui/settings_accounts.go index 8a233a3e8b4e524cbb6626e2c304893703b5d0c9..23380909303cb03bd7ffd6bba413fb62d5b01de0 100644 --- a/tui/settings_accounts.go +++ b/tui/settings_accounts.go @@ -7,6 +7,7 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + "github.com/floatpane/matcha/config" ) func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { @@ -70,6 +71,10 @@ func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { } } } + case "s": // Edit account signature + if m.accountsCursor < len(m.cfg.Accounts) { + return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: m.cfg.Accounts[m.accountsCursor].ID} } + } case "c": // Quick shortcut to crypto config if m.accountsCursor < len(m.cfg.Accounts) { m.enterCryptoConfig() @@ -139,6 +144,9 @@ func (m *Settings) viewAccounts() string { if account.PGPPublicKey != "" && account.PGPPrivateKey != "" { providerInfo += " [PGP Configured]" } + if config.HasAccountSignature(&account) { + providerInfo += " [Signature]" + } line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo)) diff --git a/tui/settings_general.go b/tui/settings_general.go index 545e06512f5d1f5e0f33d187a0cbbaa8081663d3..716e46bf51075d9035aaf95a0eb0dd99b0d1e8bd 100644 --- a/tui/settings_general.go +++ b/tui/settings_general.go @@ -9,58 +9,106 @@ import ( "github.com/floatpane/matcha/i18n" ) +type generalOption struct { + labelKey string + value string + tip string + isAccountSig bool + accountID string +} + +func (m *Settings) buildGeneralOptions() []generalOption { + opts := []generalOption{ + {"settings_general.disable_images", onOff(m.cfg.DisableImages), "Prevent images from loading automatically in emails.", false, ""}, + {"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen.", false, ""}, + {"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail.", false, ""}, + {"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed.", false, ""}, + {"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly.", false, ""}, + {"settings_general.signature", getSignatureStatus(), "Configure the global signature appended to your outgoing emails.", false, ""}, + } + + for _, acc := range m.cfg.Accounts { + status := t("settings_general.signature_not_configured") + accCopy := acc // capture for pointer safety + if config.HasAccountSignature(&accCopy) { + status = t("settings_general.signature_configured") + } + opts = append(opts, generalOption{ + labelKey: fmt.Sprintf("Signature (%s)", acc.Email), + value: status, + tip: fmt.Sprintf("Configure the signature for %s", acc.Email), + isAccountSig: true, + accountID: acc.ID, + }) + } + + return opts +} + func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { + opts := m.buildGeneralOptions() + switch msg.String() { case "up", "k": if m.generalCursor > 0 { m.generalCursor-- } case "down", "j": - if m.generalCursor < 5 { + if m.generalCursor < len(opts)-1 { m.generalCursor++ } case "enter", "space", "right", "l": - switch m.generalCursor { - case 0: // Image Display - m.cfg.DisableImages = !m.cfg.DisableImages - _ = config.SaveConfig(m.cfg) - case 1: // Contextual Tips - m.cfg.HideTips = !m.cfg.HideTips - _ = config.SaveConfig(m.cfg) - case 2: // Desktop Notifications - m.cfg.DisableNotifications = !m.cfg.DisableNotifications - _ = config.SaveConfig(m.cfg) - case 3: // Date Format - switch m.cfg.DateFormat { - case config.DateFormatEU: - m.cfg.DateFormat = config.DateFormatUS - case config.DateFormatUS: - m.cfg.DateFormat = config.DateFormatISO - default: // or ISO - m.cfg.DateFormat = config.DateFormatEU - } - _ = config.SaveConfig(m.cfg) - case 4: // Language - // Cycle through available languages - langs := i18n.LanguageCodes() - currentLang := m.cfg.GetLanguage() - currentIdx := -1 - for i, lang := range langs { - if lang == currentLang { - currentIdx = i - break + if m.generalCursor < len(opts) { + opt := opts[m.generalCursor] + if opt.isAccountSig { + if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" { + return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: opt.accountID} } } + return m, nil } - nextIdx := (currentIdx + 1) % len(langs) - m.cfg.Language = langs[nextIdx] - _ = config.SaveConfig(m.cfg) - // Apply language change immediately - i18n.GetManager().SetLanguage(m.cfg.Language) - // Trigger full UI rebuild - return m, func() tea.Msg { return LanguageChangedMsg{} } - case 5: // Edit Signature - if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" { - return m, func() tea.Msg { return GoToSignatureEditorMsg{} } + + switch m.generalCursor { + case 0: // Image Display + m.cfg.DisableImages = !m.cfg.DisableImages + _ = config.SaveConfig(m.cfg) + case 1: // Contextual Tips + m.cfg.HideTips = !m.cfg.HideTips + _ = config.SaveConfig(m.cfg) + case 2: // Desktop Notifications + m.cfg.DisableNotifications = !m.cfg.DisableNotifications + _ = config.SaveConfig(m.cfg) + case 3: // Date Format + switch m.cfg.DateFormat { + case config.DateFormatEU: + m.cfg.DateFormat = config.DateFormatUS + case config.DateFormatUS: + m.cfg.DateFormat = config.DateFormatISO + default: // or ISO + m.cfg.DateFormat = config.DateFormatEU + } + _ = config.SaveConfig(m.cfg) + case 4: // Language + // Cycle through available languages + langs := i18n.LanguageCodes() + currentLang := m.cfg.GetLanguage() + currentIdx := -1 + for i, lang := range langs { + if lang == currentLang { + currentIdx = i + break + } + } + nextIdx := (currentIdx + 1) % len(langs) + m.cfg.Language = langs[nextIdx] + _ = config.SaveConfig(m.cfg) + // Apply language change immediately + i18n.GetManager().SetLanguage(m.cfg.Language) + // Trigger full UI rebuild + return m, func() tea.Msg { return LanguageChangedMsg{} } + case 5: // Edit Signature + if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" { + return m, func() tea.Msg { return GoToSignatureEditorMsg{} } + } } } } @@ -72,18 +120,7 @@ func (m *Settings) viewGeneral() string { b.WriteString(titleStyle.Render("General Settings") + "\n\n") - options := []struct { - labelKey string - value string - tip string - }{ - {"settings_general.disable_images", onOff(m.cfg.DisableImages), "Prevent images from loading automatically in emails."}, - {"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen."}, - {"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail."}, - {"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed."}, - {"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly."}, - {"settings_general.signature", getSignatureStatus(), "Configure the signature appended to your outgoing emails."}, - } + options := m.buildGeneralOptions() for i, opt := range options { cursor := " " @@ -93,9 +130,12 @@ func (m *Settings) viewGeneral() string { style = selectedAccountItemStyle } - label := t(opt.labelKey) + label := opt.labelKey + if !opt.isAccountSig { + label = t(opt.labelKey) + } text := fmt.Sprintf("%s: %s", label, opt.value) - if opt.labelKey == "settings_general.signature" { + if opt.labelKey == "settings_general.signature" || opt.isAccountSig { text = fmt.Sprintf("%s (%s)", label, opt.value) } diff --git a/tui/signature.go b/tui/signature.go index 93fe470f501847193dbdccc8aae6d635d6be850c..29557b2cfc68a02232034bae3f9f2ea055bc9b01 100644 --- a/tui/signature.go +++ b/tui/signature.go @@ -9,13 +9,14 @@ import ( // SignatureEditor displays the signature editing screen. type SignatureEditor struct { - textarea textarea.Model - width int - height int + textarea textarea.Model + accountID string + width int + height int } // NewSignatureEditor creates a new signature editor model. -func NewSignatureEditor() *SignatureEditor { +func NewSignatureEditor(accountID string) *SignatureEditor { ta := textarea.New() ta.Placeholder = "Enter your email signature...\n\nExample:\nBest regards,\nDrew" ta.SetHeight(10) @@ -23,12 +24,19 @@ func NewSignatureEditor() *SignatureEditor { ta.Focus() // Load existing signature - if sig, err := config.LoadSignature(); err == nil && sig != "" { - ta.SetValue(sig) + if accountID != "" { + if sig, err := config.LoadRawAccountSignature(&config.Account{ID: accountID}); err == nil && sig != "" { + ta.SetValue(sig) + } + } else { + if sig, err := config.LoadSignature(); err == nil && sig != "" { + ta.SetValue(sig) + } } return &SignatureEditor{ - textarea: ta, + textarea: ta, + accountID: accountID, } } @@ -56,7 +64,11 @@ func (m *SignatureEditor) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "esc": // Save and go back to settings signature := m.textarea.Value() - go config.SaveSignature(signature) + if m.accountID != "" { + go config.SaveSignatureForAccount(m.accountID, signature) + } else { + go config.SaveSignature(signature) + } return m, func() tea.Msg { return GoToSettingsMsg{} } } }