diff --git a/tui/inbox.go b/tui/inbox.go index 52ddd15fb4fa85297384a182951fb434f38ea385..3791b3f961c5ea9ce13f4f9d6dac561e7cd5aa75 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "strings" + "time" "charm.land/bubbles/v2/key" "charm.land/bubbles/v2/list" @@ -22,12 +23,15 @@ var ( tabBarStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).PaddingBottom(1).MarginBottom(1) ) +var dateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) + type item struct { title, desc string originalIndex int uid uint32 accountID string accountEmail string + date time.Time } func (i item) Title() string { return i.title } @@ -52,6 +56,36 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list str = fmt.Sprintf("%d. [%s] %s", index+1, truncateEmail(i.accountEmail), i.title) } + // Format and right-align date + dateStr := formatRelativeDate(i.date) + 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 + } + 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] + } + str += "…" + } + + // Pad to push date to the right + padding := listWidth - lipgloss.Width(str) - dateWidth - cursorWidth + if padding < 1 { + padding = 1 + } + fn := itemStyle.Render if index == m.Index() { fn = func(s ...string) string { @@ -59,7 +93,45 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } } - fmt.Fprint(w, fn(str)) + fmt.Fprint(w, fn(str+strings.Repeat(" ", padding)+styledDate)) +} + +// formatRelativeDate formats a time as relative if within the last week, +// otherwise as an absolute date. +func formatRelativeDate(t time.Time) string { + if t.IsZero() { + return "" + } + now := time.Now() + d := now.Sub(t) + + switch { + case d < time.Minute: + return "just now" + case d < time.Hour: + mins := int(d.Minutes()) + if mins == 1 { + return "1 min ago" + } + return fmt.Sprintf("%d min ago", mins) + case d < 24*time.Hour: + hours := int(d.Hours()) + if hours == 1 { + return "1 hour ago" + } + return fmt.Sprintf("%d hours ago", hours) + case d < 7*24*time.Hour: + days := int(d.Hours() / 24) + if days == 1 { + return "1 day ago" + } + return fmt.Sprintf("%d days ago", days) + default: + if t.Year() == now.Year() { + return t.Format("Jan 02") + } + return t.Format("Jan 02, 2006") + } } // truncateEmail shortens an email for display @@ -205,6 +277,7 @@ func (m *Inbox) updateList() { uid: email.UID, accountID: email.AccountID, accountEmail: accountEmail, + date: email.Date, } }