Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions config/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,109 @@ func MigrateContactsCacheUsage(accountIDs []string) error {
return SaveContactsCache(cache)
}

// GetNamedContacts returns all contacts with an explicit name, sorted alphabetically.
func GetNamedContacts() []Contact {
cache, err := LoadContactsCache()
if err != nil {
return nil
}
var result []Contact
for _, c := range cache.Contacts {
name := strings.TrimSpace(c.Name)
if name != "" && !strings.EqualFold(name, c.Email) {
result = append(result, c)
}
}
sort.Slice(result, func(i, j int) bool {
return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name)
})
return result
}

// SearchUnnamedContacts returns up to 5 contacts without an explicit name,
// matching query against the email field, sorted by usage. Used for email
// field autofill in the contact editor.
func SearchUnnamedContacts(query string) []Contact {
cache, err := LoadContactsCache()
if err != nil {
return nil
}
query = strings.ToLower(strings.TrimSpace(query))
if query == "" {
return nil
}
var matches []Contact
for _, c := range cache.Contacts {
name := strings.TrimSpace(c.Name)
isUnnamed := name == "" || strings.EqualFold(name, c.Email)
if !isUnnamed {
continue
}
if strings.Contains(strings.ToLower(c.Email), query) {
matches = append(matches, c)
}
}
sort.Slice(matches, func(i, j int) bool {
left := ContactAggregateUsage(matches[i])
right := ContactAggregateUsage(matches[j])
if left.UseCount != right.UseCount {
return left.UseCount > right.UseCount
}
return left.LastUsed.After(right.LastUsed)
})
if len(matches) > 5 {
matches = matches[:5]
}
return matches
}

// DeleteContact removes the contact with the given email from the cache.
func DeleteContact(email string) error {
cache, err := LoadContactsCache()
if err != nil {
return err
}
email = normalizeContactEmail(email)
filtered := cache.Contacts[:0]
for _, c := range cache.Contacts {
if !strings.EqualFold(c.Email, email) {
filtered = append(filtered, c)
}
}
cache.Contacts = filtered
return SaveContactsCache(cache)
}

// UpdateContact updates the name and email of an existing contact identified
// by originalEmail. Returns an error if the contact is not found or if the
// new email collides with a different existing contact.
func UpdateContact(originalEmail, newName, newEmail string) error {
cache, err := LoadContactsCache()
if err != nil {
return err
}
originalEmail = normalizeContactEmail(originalEmail)
newEmail = normalizeContactEmail(newEmail)
newName = strings.TrimSpace(newName)

if !strings.EqualFold(originalEmail, newEmail) {
for _, c := range cache.Contacts {
if strings.EqualFold(c.Email, newEmail) {
return errors.New("a contact with that email already exists")
}
}
}

for i, c := range cache.Contacts {
if strings.EqualFold(c.Email, originalEmail) {
cache.Contacts[i].Name = newName
cache.Contacts[i].Email = newEmail
return SaveContactsCache(cache)
}
}
return errors.New("contact not found")
}

func removeAccountFromContactsCache(accountID string) error {
cache, err := LoadContactsCache()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@
"category_mailing_lists": "القوائم البريدية",
"category_encryption": "تشفير التطبيق",
"category_plugins": "الإضافات",
"category_contacts": "جهات الاتصال",
"help_menu": "↑/↓: التنقل • يمين/enter: اختيار • esc: رجوع",
"help_content": "left/esc: العودة للقائمة"
},
"settings_contacts": {
"title": "دفتر جهات الاتصال",
"no_contacts": "لا توجد جهات اتصال مسماة بعد.",
"add_contact": "إضافة جهة اتصال جديدة",
"delete_confirm": "حذف جهة الاتصال؟",
"help": "↑/↓: التنقل • e/enter: تعديل • d: حذف • enter على +: إضافة • esc: القائمة"
},
"settings_accounts": {
"title": "إعدادات الحسابات",
"no_accounts": "لم يتم تكوين حسابات.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@
"category_mailing_lists": "Mailinglisten",
"category_encryption": "App-Verschlüsselung",
"category_plugins": "Plugins",
"category_contacts": "Kontakte",
"help_menu": "↑/↓: navigieren • rechts/enter: auswählen • esc: zurück",
"help_content": "left/esc: zurück zum Menü"
},
"settings_contacts": {
"title": "Kontaktbuch",
"no_contacts": "Noch keine benannten Kontakte.",
"add_contact": "Neuen Kontakt Hinzufügen",
"delete_confirm": "Kontakt löschen?",
"help": "↑/↓: navigieren • e/enter: bearbeiten • d: löschen • enter auf +: hinzufügen • esc: Menü"
},
"settings_accounts": {
"title": "Kontoeinstellungen",
"no_accounts": "Keine Konten konfiguriert.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@
"category_mailing_lists": "Mailing Lists",
"category_encryption": "App Encryption",
"category_plugins": "Plugins",
"category_contacts": "Contacts",
"help_menu": "↑/↓: navigate • right/enter: select • esc: go back",
"help_content": "left/esc: back to menu"
},
"settings_contacts": {
"title": "Contact Book",
"no_contacts": "No named contacts yet.",
"add_contact": "Add New Contact",
"delete_confirm": "Delete contact?",
"help": "↑/↓: navigate • e/enter: edit • d: delete • enter on +: add • esc: menu"
},
"settings_accounts": {
"title": "Account Settings",
"no_accounts": "No accounts configured.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@
"category_mailing_lists": "Listas de Correo",
"category_encryption": "Cifrado de Aplicación",
"category_plugins": "Plugins",
"category_contacts": "Contactos",
"help_menu": "↑/↓: navegar • derecha/enter: seleccionar • esc: volver",
"help_content": "left/esc: volver al menú"
},
"settings_contacts": {
"title": "Libreta de Contactos",
"no_contacts": "Aún no hay contactos con nombre.",
"add_contact": "Agregar Nuevo Contacto",
"delete_confirm": "¿Eliminar contacto?",
"help": "↑/↓: navegar • e/enter: editar • d: eliminar • enter en +: agregar • esc: menú"
},
"settings_accounts": {
"title": "Configuración de Cuentas",
"no_accounts": "No hay cuentas configuradas.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@
"category_mailing_lists": "Listes de Diffusion",
"category_encryption": "Chiffrement de l'Application",
"category_plugins": "Plugins",
"category_contacts": "Contacts",
"help_menu": "↑/↓: naviguer • droite/entrée: sélectionner • esc: retour",
"help_content": "left/esc: retour au menu"
},
"settings_contacts": {
"title": "Carnet de Contacts",
"no_contacts": "Aucun contact nommé pour l'instant.",
"add_contact": "Ajouter un Nouveau Contact",
"delete_confirm": "Supprimer le contact ?",
"help": "↑/↓: naviguer • e/entrée: modifier • d: supprimer • entrée sur +: ajouter • esc: menu"
},
"settings_accounts": {
"title": "Paramètres des Comptes",
"no_accounts": "Aucun compte configuré.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,17 @@
"category_mailing_lists": "メーリングリスト",
"category_encryption": "アプリの暗号化",
"category_plugins": "プラグイン",
"category_contacts": "連絡先",
"help_menu": "↑/↓: 移動 • 右/enter: 選択 • esc: 戻る",
"help_content": "left/esc: メニューに戻る"
},
"settings_contacts": {
"title": "連絡先帳",
"no_contacts": "名前付きの連絡先はまだありません。",
"add_contact": "新しい連絡先を追加",
"delete_confirm": "連絡先を削除しますか?",
"help": "↑/↓: 移動 • e/enter: 編集 • d: 削除 • +でenter: 追加 • esc: メニュー"
},
"settings_accounts": {
"title": "アカウント設定",
"no_accounts": "アカウントが設定されていません。",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@
"category_mailing_lists": "Listy Mailingowe",
"category_encryption": "Szyfrowanie Aplikacji",
"category_plugins": "Wtyczki",
"category_contacts": "Kontakty",
"help_menu": "↑/↓: nawigacja • prawo/enter: wybierz • esc: wstecz",
"help_content": "left/esc: powrót do menu"
},
"settings_contacts": {
"title": "Książka Kontaktów",
"no_contacts": "Brak nazwanych kontaktów.",
"add_contact": "Dodaj Nowy Kontakt",
"delete_confirm": "Usunąć kontakt?",
"help": "↑/↓: nawigacja • e/enter: edytuj • d: usuń • enter na +: dodaj • esc: menu"
},
"settings_accounts": {
"title": "Ustawienia Kont",
"no_accounts": "Brak skonfigurowanych kont.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@
"category_mailing_lists": "Listas de E-mail",
"category_encryption": "Criptografia do Aplicativo",
"category_plugins": "Plugins",
"category_contacts": "Contatos",
"help_menu": "↑/↓: navegar • direita/enter: selecionar • esc: voltar",
"help_content": "left/esc: voltar ao menu"
},
"settings_contacts": {
"title": "Agenda de Contatos",
"no_contacts": "Nenhum contato nomeado ainda.",
"add_contact": "Adicionar Novo Contato",
"delete_confirm": "Excluir contato?",
"help": "↑/↓: navegar • e/enter: editar • d: excluir • enter em +: adicionar • esc: menu"
},
"settings_accounts": {
"title": "Configurações de Contas",
"no_accounts": "Nenhuma conta configurada.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@
"category_mailing_lists": "Списки Рассылки",
"category_encryption": "Шифрование Приложения",
"category_plugins": "Плагины",
"category_contacts": "Контакты",
"help_menu": "↑/↓: навигация • вправо/enter: выбор • esc: назад",
"help_content": "left/esc: назад в меню"
},
"settings_contacts": {
"title": "Книга контактов",
"no_contacts": "Именованных контактов пока нет.",
"add_contact": "Добавить новый контакт",
"delete_confirm": "Удалить контакт?",
"help": "↑/↓: навигация • e/enter: редактировать • d: удалить • enter на +: добавить • esc: меню"
},
"settings_accounts": {
"title": "Настройки Учётных Записей",
"no_accounts": "Учётные записи не настроены.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,17 @@
"category_mailing_lists": "Списки розсилки",
"category_encryption": "Шифрування додатка",
"category_plugins": "Плагіни",
"category_contacts": "Контакти",
"help_menu": "↑/↓: навігація • right/enter: вибрати • esc: назад",
"help_content": "left/esc: назад до меню"
},
"settings_contacts": {
"title": "Книга контактів",
"no_contacts": "Іменованих контактів ще немає.",
"add_contact": "Додати новий контакт",
"delete_confirm": "Видалити контакт?",
"help": "↑/↓: навігація • e/enter: редагувати • d: видалити • enter на +: додати • esc: меню"
},
"settings_accounts": {
"title": "Налаштування облікових записів",
"no_accounts": "Облікові записи не налаштовано.",
Expand Down
8 changes: 8 additions & 0 deletions i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,17 @@
"category_mailing_lists": "邮件列表",
"category_encryption": "应用加密",
"category_plugins": "插件",
"category_contacts": "联系人",
"help_menu": "↑/↓: 导航 • 右/enter: 选择 • esc: 返回",
"help_content": "left/esc: 返回菜单"
},
"settings_contacts": {
"title": "通讯录",
"no_contacts": "尚无命名联系人。",
"add_contact": "添加新联系人",
"delete_confirm": "删除联系人?",
"help": "↑/↓: 导航 • e/enter: 编辑 • d: 删除 • 在+按enter: 添加 • esc: 菜单"
},
"settings_accounts": {
"title": "账户设置",
"no_accounts": "未配置账户。",
Expand Down
29 changes: 28 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,33 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo
m.current, _ = m.current.Update(m.currentWindowSize())
return m, m.current.Init()

case tui.GoToAddContactMsg:
m.current = tui.NewContactEditor()
m.current, _ = m.current.Update(m.currentWindowSize())
return m, m.current.Init()

case tui.GoToEditContactMsg:
editor := tui.NewContactEditor()
editor.SetEditMode(msg.OriginalEmail, msg.Name, msg.Email)
m.current = editor
m.current, _ = m.current.Update(m.currentWindowSize())
return m, m.current.Init()

case tui.SaveContactMsg:
if msg.IsEdit {
_ = config.UpdateContact(msg.OriginalEmail, msg.Name, msg.Email)
} else {
_ = config.AddContact(msg.Name, msg.Email)
}
s := m.newSettings()
s.RestoreState(tui.SettingsState{
ActivePane: tui.PaneContent,
ActiveCategory: tui.CategoryContacts,
})
m.current = s
m.current, _ = m.current.Update(m.currentWindowSize())
return m, m.current.Init()

case tui.SaveMailingListMsg:
if m.config != nil {
var addrs []string
Expand Down Expand Up @@ -2240,7 +2267,7 @@ func (m *mainModel) closePalette() {
// would interfere with typing) and while an inbox search/filter is active.
func (m *mainModel) paletteAllowed() bool {
switch v := m.current.(type) {
case *tui.Composer, *tui.Login, *tui.SignatureEditor, *tui.MailingListEditor,
case *tui.Composer, *tui.Login, *tui.SignatureEditor, *tui.MailingListEditor, *tui.ContactEditor,
*tui.PasswordPrompt, *tui.FilePicker, *tui.Status:
return false
case *tui.Inbox:
Expand Down
Loading