diff --git a/config/config.go b/config/config.go index 0cdce5c8f0ca2881c7cac32dad62bfe0fc516c19..9ca0a041af314bd355d4fe4fe4cc05c87f9a2dc0 100644 --- a/config/config.go +++ b/config/config.go @@ -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 diff --git a/docs/docs/Configuration.md b/docs/docs/Configuration.md index 1ca767cb04771d2d3ffcf12f4c16ebce6895f182..e37c70f09b8fd4d2ce383c7948259f4ad11e9b37 100644 --- a/docs/docs/Configuration.md +++ b/docs/docs/Configuration.md @@ -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 diff --git a/i18n/locales/ar.json b/i18n/locales/ar.json index 44b011b4619e3e7e59f277a56aa5daba4276b36b..ac7ac44fb9420e52610ea6cfa2e62a0786a59213 100644 --- a/i18n/locales/ar.json +++ b/i18n/locales/ar.json @@ -153,6 +153,7 @@ "disable_notifications": "تعطيل الإشعارات", "enable_split_pane": "عرض مقسم", "enable_threaded": "عرض المحادثات", + "enable_detailed_dates": "تواريخ مفصلة", "date_format": "تنسيق التاريخ", "language": "اللغة", "signature": "تعديل التوقيع", diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 579a00d226ab0a438ebe7084c3286b4b7c16a30f..6a96076c8904d5baab3f7014ea989bc156c38e34 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -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", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 68426d73a3038c52d5e6034dc767507d0dab35a0..4fcf69b26d5e7f22d393aae094f43906c0944221 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -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", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index 24d23e7d4aeb54dbb903c7803c8bc2fa38104617..88a07ef18b7d5dc5bbff14a4c69adb40efee781c 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -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", diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json index e026c028190513cf5aeffb8a5709ed5f04477ac8..6ad0a8fd33cb9849ae956ec33e7fe96d35f676e0 100644 --- a/i18n/locales/fr.json +++ b/i18n/locales/fr.json @@ -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", diff --git a/i18n/locales/ja.json b/i18n/locales/ja.json index a4e23caf5cedeb161857caff029b286146afa729..9556af911d8f7ae0fffa9077b567bb5d0a6a00c1 100644 --- a/i18n/locales/ja.json +++ b/i18n/locales/ja.json @@ -147,6 +147,7 @@ "disable_notifications": "通知を無効化", "enable_split_pane": "分割ビュー", "enable_threaded": "スレッド表示", + "enable_detailed_dates": "詳細な日付", "date_format": "日付形式", "language": "言語", "signature": "署名を編集", diff --git a/i18n/locales/pl.json b/i18n/locales/pl.json index a69377cc9a307c9541cef0c5cda9385ca5a8caa1..d4ea5a6ddc82104a5bff5d0f3df4ef5481705c45 100644 --- a/i18n/locales/pl.json +++ b/i18n/locales/pl.json @@ -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", diff --git a/i18n/locales/pt.json b/i18n/locales/pt.json index 023286f050b331fe7da7b71b550cd7b2dbe95d62..a9a8663210255b2706bb52811ce312083d457fde 100644 --- a/i18n/locales/pt.json +++ b/i18n/locales/pt.json @@ -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", diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index 5233f0210cd7a9403cfd94e9fdd6a7b5a0bd122c..ddaf0e0d8bd975390f5461dcfbc9228bee6510b0 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -153,6 +153,7 @@ "disable_notifications": "Отключить Уведомления", "enable_split_pane": "Разделённый вид", "enable_threaded": "Просмотр беседами", + "enable_detailed_dates": "Подробные даты", "date_format": "Формат Даты", "language": "Язык", "signature": "Редактировать Подпись", diff --git a/i18n/locales/uk.json b/i18n/locales/uk.json index 203303da074225012ffd51a5a676873843abab28..cf6c1f4b831bf0745c5cdf631266423f1cfcc776 100644 --- a/i18n/locales/uk.json +++ b/i18n/locales/uk.json @@ -151,6 +151,7 @@ "disable_notifications": "Вимкнути сповіщення", "enable_split_pane": "Розділений вигляд", "enable_threaded": "Перегляд розмов", + "enable_detailed_dates": "Детальні дати", "date_format": "Формат дати", "language": "Мова", "signature": "Редагувати підпис", diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index 4d07bcae6b0a68cfd0ed9c91e2a74cabf0c793d6..730d46f282e7785610e319a69f1d0fa13e5dfb51 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -147,6 +147,7 @@ "disable_notifications": "禁用通知", "enable_split_pane": "分屏视图", "enable_threaded": "会话视图", + "enable_detailed_dates": "详细日期", "date_format": "日期格式", "language": "语言", "signature": "编辑签名", diff --git a/main.go b/main.go index 26df2d00b7fc38e03ba557f670687444e3f85e8c..fecd79575741df1c69dad7cff7205254ffe11ade 100644 --- a/main.go +++ b/main.go @@ -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 diff --git a/tui/folder_inbox.go b/tui/folder_inbox.go index 3069d87b95e06665fc3b99dd455b20365e1e56e2..529d5d80243936d456e85d132ddef6206bfb3d68 100644 --- a/tui/folder_inbox.go +++ b/tui/folder_inbox.go @@ -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 { diff --git a/tui/inbox.go b/tui/inbox.go index 0477271f4eaaf7cc93bda7e45847b28862975108..9fe1854d55e8e6da8c8bdb61703bc462940af887 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -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 " 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) } diff --git a/tui/settings_general.go b/tui/settings_general.go index e242022d04678d2e2194df863fa1e8e1498977ca..bf7d4a7f0705921edc2ee303202a08d8a12c4f85 100644 --- a/tui/settings_general.go +++ b/tui/settings_general.go @@ -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{} } }