Detailed changes
@@ -93,6 +93,7 @@ type Config struct {
DisableNotifications bool `json:"disable_notifications,omitempty"`
EnableSplitPane bool `json:"enable_split_pane,omitempty"`
EnableThreaded bool `json:"enable_threaded,omitempty"`
+ EnableDetailedDates bool `json:"enable_detailed_dates,omitempty"`
Theme string `json:"theme,omitempty"`
MailingLists []MailingList `json:"mailing_lists,omitempty"`
DateFormat string `json:"date_format,omitempty"`
@@ -405,6 +406,7 @@ type secureDiskConfig struct {
DisableNotifications bool `json:"disable_notifications,omitempty"`
EnableSplitPane bool `json:"enable_split_pane,omitempty"`
EnableThreaded bool `json:"enable_threaded,omitempty"`
+ EnableDetailedDates bool `json:"enable_detailed_dates,omitempty"`
Theme string `json:"theme,omitempty"`
MailingLists []MailingList `json:"mailing_lists,omitempty"`
DateFormat string `json:"date_format,omitempty"`
@@ -451,6 +453,8 @@ func SaveConfig(config *Config) error {
HideTips: config.HideTips,
DisableNotifications: config.DisableNotifications,
EnableSplitPane: config.EnableSplitPane,
+ EnableThreaded: config.EnableThreaded,
+ EnableDetailedDates: config.EnableDetailedDates,
Theme: config.Theme,
MailingLists: config.MailingLists,
DateFormat: config.DateFormat,
@@ -554,6 +558,7 @@ func LoadConfig() (*Config, error) {
DisableNotifications bool `json:"disable_notifications,omitempty"`
EnableSplitPane bool `json:"enable_split_pane,omitempty"`
EnableThreaded bool `json:"enable_threaded,omitempty"`
+ EnableDetailedDates bool `json:"enable_detailed_dates,omitempty"`
Theme string `json:"theme,omitempty"`
MailingLists []MailingList `json:"mailing_lists,omitempty"`
DateFormat string `json:"date_format,omitempty"`
@@ -592,6 +597,7 @@ func LoadConfig() (*Config, error) {
config.DisableNotifications = raw.DisableNotifications
config.EnableSplitPane = raw.EnableSplitPane
config.EnableThreaded = raw.EnableThreaded
+ config.EnableDetailedDates = raw.EnableDetailedDates
config.Theme = raw.Theme
config.MailingLists = raw.MailingLists
config.DateFormat = raw.DateFormat
@@ -44,6 +44,8 @@ Configuration is stored in `~/.config/matcha/config.json`.
],
"theme": "Matcha",
"enable_split_pane": true,
+ "enable_detailed_dates": true,
+ "date_format": "DD/MM/YYYY HH:MM",
"disable_images": true,
"hide_tips": true,
"body_cache_threshold_mb": 100
@@ -54,6 +56,8 @@ Configuration is stored in `~/.config/matcha/config.json`.
`enable_split_pane` enables a side-by-side view where the email list and the selected email are shown on the same screen.
+`enable_detailed_dates` shows absolute inbox dates using your configured `date_format` instead of relative labels like "2 hours ago".
+
`body_cache_threshold_mb` sets the maximum size (in megabytes) for the local email body cache. When this limit is reached, least recently accessed cached emails are evicted across all folders to make room for new ones. Defaults to `100` MB if not specified.
## Data Locations
@@ -153,6 +153,7 @@
"disable_notifications": "تعطيل الإشعارات",
"enable_split_pane": "عرض مقسم",
"enable_threaded": "عرض المحادثات",
+ "enable_detailed_dates": "تواريخ مفصلة",
"date_format": "تنسيق التاريخ",
"language": "اللغة",
"signature": "تعديل التوقيع",
@@ -149,6 +149,7 @@
"disable_notifications": "Benachrichtigungen Deaktivieren",
"enable_split_pane": "Geteilte Ansicht",
"enable_threaded": "Konversations-Threads",
+ "enable_detailed_dates": "Detaillierte Datumsangaben",
"date_format": "Datumsformat",
"language": "Sprache",
"signature": "Signatur Bearbeiten",
@@ -149,6 +149,7 @@
"disable_notifications": "Disable Notifications",
"enable_split_pane": "Split Pane View",
"enable_threaded": "Threaded Conversation View",
+ "enable_detailed_dates": "Detailed Dates",
"date_format": "Date Format",
"language": "Language",
"signature": "Edit Signature",
@@ -149,6 +149,7 @@
"disable_notifications": "Deshabilitar Notificaciones",
"enable_split_pane": "Vista dividida",
"enable_threaded": "Vista de conversación",
+ "enable_detailed_dates": "Fechas detalladas",
"date_format": "Formato de Fecha",
"language": "Idioma",
"signature": "Editar Firma",
@@ -149,6 +149,7 @@
"disable_notifications": "Désactiver les Notifications",
"enable_split_pane": "Vue divisée",
"enable_threaded": "Vue par conversation",
+ "enable_detailed_dates": "Dates détaillées",
"date_format": "Format de Date",
"language": "Langue",
"signature": "Modifier la Signature",
@@ -147,6 +147,7 @@
"disable_notifications": "通知を無効化",
"enable_split_pane": "分割ビュー",
"enable_threaded": "スレッド表示",
+ "enable_detailed_dates": "詳細な日付",
"date_format": "日付形式",
"language": "言語",
"signature": "署名を編集",
@@ -153,6 +153,7 @@
"disable_notifications": "Wyłącz Powiadomienia",
"enable_split_pane": "Widok podzielony",
"enable_threaded": "Widok wątków",
+ "enable_detailed_dates": "Szczegółowe daty",
"date_format": "Format Daty",
"language": "Język",
"signature": "Edytuj Podpis",
@@ -149,6 +149,7 @@
"disable_notifications": "Desativar Notificações",
"enable_split_pane": "Vista dividida",
"enable_threaded": "Vista de conversação",
+ "enable_detailed_dates": "Datas detalhadas",
"date_format": "Formato de Data",
"language": "Idioma",
"signature": "Editar Assinatura",
@@ -153,6 +153,7 @@
"disable_notifications": "Отключить Уведомления",
"enable_split_pane": "Разделённый вид",
"enable_threaded": "Просмотр беседами",
+ "enable_detailed_dates": "Подробные даты",
"date_format": "Формат Даты",
"language": "Язык",
"signature": "Редактировать Подпись",
@@ -151,6 +151,7 @@
"disable_notifications": "Вимкнути сповіщення",
"enable_split_pane": "Розділений вигляд",
"enable_threaded": "Перегляд розмов",
+ "enable_detailed_dates": "Детальні дати",
"date_format": "Формат дати",
"language": "Мова",
"signature": "Редагувати підпис",
@@ -147,6 +147,7 @@
"disable_notifications": "禁用通知",
"enable_split_pane": "分屏视图",
"enable_threaded": "会话视图",
+ "enable_detailed_dates": "详细日期",
"date_format": "日期格式",
"language": "语言",
"signature": "编辑签名",
@@ -493,6 +493,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.folderInbox = tui.NewFolderInbox(cachedFolders, m.config.Accounts)
m.folderInbox.SetDateFormat(m.config.GetDateFormat())
+ m.folderInbox.SetDetailedDates(m.config.EnableDetailedDates)
m.folderInbox.SetDefaultThreaded(m.config.EnableThreaded)
// Use cached INBOX emails for instant display (memory first, then disk)
if cached, ok := m.folderEmails["INBOX"]; ok && len(cached) > 0 {
@@ -1088,6 +1089,8 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
if m.folderInbox != nil {
+ m.folderInbox.SetDateFormat(m.config.GetDateFormat())
+ m.folderInbox.SetDetailedDates(m.config.EnableDetailedDates)
m.folderInbox.SetDefaultThreaded(m.config.EnableThreaded)
}
return m, nil
@@ -133,6 +133,13 @@ func (m *FolderInbox) SetDateFormat(layout string) {
}
}
+// SetDetailedDates propagates the detailed date display toggle.
+func (m *FolderInbox) SetDetailedDates(enabled bool) {
+ if m.inbox != nil {
+ m.inbox.SetDetailedDates(enabled)
+ }
+}
+
// SetDefaultThreaded propagates the global default threading toggle.
func (m *FolderInbox) SetDefaultThreaded(v bool) {
if m.inbox != nil {
@@ -105,10 +105,12 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
// Format and right-align date
layout := ""
+ detailedDates := false
if d.inbox != nil {
layout = d.inbox.dateFormat
+ detailedDates = d.inbox.detailedDates
}
- dateStr := formatRelativeDate(i.date, layout)
+ dateStr := formatInboxDate(i.date, layout, detailedDates)
listWidth := m.Width() - 2 // account for PaddingLeft(2) in itemStyle
isSelected := index == m.Index()
@@ -215,14 +217,18 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
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 using the caller-supplied Go time layout.
+// formatInboxDate formats a time as relative unless detailed dates are enabled
+// or the timestamp is older than a week. Absolute dates use the caller-supplied
+// Go time layout.
// When layout is empty, falls back to the built-in short/long defaults.
-func formatRelativeDate(timestamp time.Time, layout string) string {
+func formatInboxDate(timestamp time.Time, layout string, detailedDates bool) string {
if timestamp.IsZero() {
return ""
}
now := time.Now()
+ if detailedDates {
+ return formatAbsoluteDate(timestamp, layout, now)
+ }
d := now.Sub(timestamp)
switch {
@@ -238,15 +244,19 @@ func formatRelativeDate(timestamp time.Time, layout string) string {
days := int(d.Hours() / 24)
return tn("time.day_ago", days, map[string]interface{}{"count": days})
default:
- timestamp = timestamp.Local()
- if layout != "" {
- return timestamp.Format(layout)
- }
- if timestamp.Year() == now.Year() {
- return timestamp.Format("Jan 02")
- }
- return timestamp.Format("Jan 02, 2006")
+ return formatAbsoluteDate(timestamp, layout, now)
+ }
+}
+
+func formatAbsoluteDate(timestamp time.Time, layout string, now time.Time) string {
+ timestamp = timestamp.Local()
+ if layout != "" {
+ return timestamp.Format(layout)
+ }
+ if timestamp.Year() == now.Year() {
+ return timestamp.Format("Jan 02")
}
+ return timestamp.Format("Jan 02, 2006")
}
// parseSenderName extracts the display name from a "Name <email>" string,
@@ -332,7 +342,8 @@ type Inbox struct {
// dateFormat is the Go reference-time layout used for absolute dates
// older than a week. When empty, the built-in defaults apply.
- dateFormat string
+ dateFormat string
+ detailedDates bool
}
// SetDateFormat configures the Go time layout used to render absolute
@@ -342,6 +353,13 @@ func (m *Inbox) SetDateFormat(layout string) {
m.dateFormat = layout
}
+// SetDetailedDates configures whether the email list should always render
+// absolute dates instead of recent relative dates.
+func (m *Inbox) SetDetailedDates(enabled bool) {
+ m.detailedDates = enabled
+ m.updateList()
+}
+
func NewInbox(emails []fetcher.Email, accounts []config.Account) *Inbox {
return NewInboxWithMailbox(emails, accounts, MailboxInbox)
}
@@ -24,6 +24,7 @@ func (m *Settings) buildGeneralOptions() []generalOption {
{"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail.", false, ""},
{"settings_general.enable_split_pane", onOff(m.cfg.EnableSplitPane), "View inbox and email side-by-side.", false, ""},
{"settings_general.enable_threaded", onOff(m.cfg.EnableThreaded), "Group emails into conversations by reply chain. Per-folder overrides are kept.", false, ""},
+ {"settings_general.enable_detailed_dates", onOff(m.cfg.EnableDetailedDates), "Show detailed inbox dates.", 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, ""},
@@ -87,7 +88,11 @@ func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
m.cfg.EnableThreaded = !m.cfg.EnableThreaded
_ = config.SaveConfig(m.cfg)
saved = true
- case 5: // Date Format
+ case 5: // Detailed Dates
+ m.cfg.EnableDetailedDates = !m.cfg.EnableDetailedDates
+ _ = config.SaveConfig(m.cfg)
+ saved = true
+ case 6: // Date Format
switch m.cfg.DateFormat {
case config.DateFormatEU:
m.cfg.DateFormat = config.DateFormatUS
@@ -98,7 +103,7 @@ func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
}
_ = config.SaveConfig(m.cfg)
saved = true
- case 6: // Language
+ case 7: // Language
// Cycle through available languages
langs := i18n.LanguageCodes()
currentLang := m.cfg.GetLanguage()
@@ -119,7 +124,7 @@ func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
func() tea.Msg { return ConfigSavedMsg{} },
func() tea.Msg { return LanguageChangedMsg{} },
)
- case 7: // Edit Signature
+ case 8: // Edit Signature
if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
return m, func() tea.Msg { return GoToSignatureEditorMsg{} }
}