From 6bbe6c94ca1c1c6ace3a223a86c02cc205e4a3ce Mon Sep 17 00:00:00 2001 From: Drew Smirnoff Date: Tue, 10 Mar 2026 10:37:55 +0400 Subject: [PATCH] feat: From in inbox view (#268) --- fetcher/fetcher.go | 14 +++++++++++-- tui/inbox.go | 49 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index b4ac57e4bec69252d5f89731f2ad358b4130c37c..a3aae35b591ee18c809cfdd4cc0f9d0d5566a058 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -59,6 +59,16 @@ type Folder struct { Attributes []string } +// formatAddress returns "Name " when a PersonalName is present, +// otherwise just "email". +func formatAddress(addr *imap.Address) string { + email := addr.Address() + if addr.PersonalName != "" { + return addr.PersonalName + " <" + email + ">" + } + return email +} + func decodePart(reader io.Reader, header mail.PartHeader) (string, error) { mediaType, params, err := mime.ParseMediaType(header.Get("Content-Type")) if err != nil { @@ -275,7 +285,7 @@ func FetchMailboxEmails(account *config.Account, mailbox string, limit, offset u var fromAddr string if len(msg.Envelope.From) > 0 { - fromAddr = msg.Envelope.From[0].Address() + fromAddr = formatAddress(msg.Envelope.From[0]) } var toAddrList []string @@ -1079,7 +1089,7 @@ func FetchArchiveEmails(account *config.Account, limit, offset uint32) ([]Email, var fromAddr string if len(msg.Envelope.From) > 0 { - fromAddr = msg.Envelope.From[0].Address() + fromAddr = formatAddress(msg.Envelope.From[0]) } var toAddrList []string diff --git a/tui/inbox.go b/tui/inbox.go index 3791b3f961c5ea9ce13f4f9d6dac561e7cd5aa75..a16dfd6222fc6d3cf373c2f16f67dc464a16510c 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -23,7 +23,8 @@ var ( tabBarStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).PaddingBottom(1).MarginBottom(1) ) -var dateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) +var dateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) +var senderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Bold(true) type item struct { title, desc string @@ -49,11 +50,14 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list return } - str := fmt.Sprintf("%d. %s", index+1, i.title) + prefix := fmt.Sprintf("%d. ", index+1) + sender := parseSenderName(i.desc) + styledSender := senderStyle.Render(sender) + separator := " · " - // For "ALL" view, show account indicator + // For "ALL" view, show account indicator instead of number if i.accountEmail != "" { - str = fmt.Sprintf("%d. [%s] %s", index+1, truncateEmail(i.accountEmail), i.title) + prefix = fmt.Sprintf("%d. [%s] ", index+1, truncateEmail(i.accountEmail)) } // Format and right-align date @@ -61,25 +65,37 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list styledDate := dateStyle.Render(dateStr) dateWidth := lipgloss.Width(styledDate) - // Truncate the left part to fit within the available width listWidth := m.Width() isSelected := index == m.Index() cursorWidth := 0 if isSelected { cursorWidth = 2 // "> " prefix } + + // Available width for the whole left side (prefix + sender + separator + subject) maxLeft := listWidth - dateWidth - 2 - cursorWidth // 2 for spacing if maxLeft < 10 { maxLeft = 10 } - if lipgloss.Width(str) > maxLeft { - // Truncate with ellipsis - for lipgloss.Width(str) > maxLeft-1 && len(str) > 0 { - str = str[:len(str)-1] + + prefixWidth := lipgloss.Width(prefix) + senderWidth := lipgloss.Width(styledSender) + sepWidth := len(separator) + subjectBudget := maxLeft - prefixWidth - senderWidth - sepWidth + + subject := i.title + if subjectBudget < 4 { + subjectBudget = 4 + } + if lipgloss.Width(subject) > subjectBudget { + for lipgloss.Width(subject) > subjectBudget-1 && len(subject) > 0 { + subject = subject[:len(subject)-1] } - str += "…" + subject += "…" } + str := prefix + styledSender + separator + subject + // Pad to push date to the right padding := listWidth - lipgloss.Width(str) - dateWidth - cursorWidth if padding < 1 { @@ -134,6 +150,19 @@ func formatRelativeDate(t time.Time) string { } } +// parseSenderName extracts the display name from a "Name " string, +// falling back to the local part of the email address. +func parseSenderName(from string) string { + if idx := strings.Index(from, " <"); idx > 0 { + return strings.TrimSpace(from[:idx]) + } + // No display name — use local part of email + if idx := strings.Index(from, "@"); idx > 0 { + return from[:idx] + } + return from +} + // truncateEmail shortens an email for display func truncateEmail(email string) string { parts := strings.Split(email, "@")