package inbox import ( "fmt" "io" "time" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) type EmailItem struct { UID uint32 From string FromName string Subject string Date time.Time Preview string Account string AccountID string } func (e EmailItem) FilterValue() string { return fmt.Sprintf("%s %s %s %s", e.Account, e.FromName, e.From, e.Subject) } func (e EmailItem) Title() string { return fmt.Sprintf("%s · %s - %s", e.Account, e.FromName, e.Subject) } func (e EmailItem) Description() string { return e.Preview } func formatTimeAgo(d time.Duration) string { if d < time.Minute { return "刚刚" } if d < time.Hour { mins := int(d.Minutes()) return fmt.Sprintf("%d分钟前", mins) } if d < 24*time.Hour { hours := int(d.Hours()) return fmt.Sprintf("%d小时前", hours) } if d < 7*24*time.Hour { days := int(d.Hours() / 24) return fmt.Sprintf("%d天前", days) } return d.Truncate(24 * time.Hour).String() } type InboxModel struct { list list.Model emails []EmailItem loading bool err error selectedEmail *EmailItem } func NewInboxModel() *InboxModel { l := list.New(nil, emailDelegate{}, 0, 10) l.Title = "收件箱" l.Styles.Title = inboxTitleStyle l.Styles.NoItems = inboxNoItemsStyle l.SetShowHelp(true) l.SetShowPagination(false) return &InboxModel{ list: l, emails: []EmailItem{}, } } func (m *InboxModel) SetEmails(emails []EmailItem) { m.emails = emails items := make([]list.Item, len(emails)) for i, e := range emails { items[i] = e } m.list.SetItems(items) m.list.Title = fmt.Sprintf("收件箱 (%d 封新邮件)", len(items)) } func (m InboxModel) Init() tea.Cmd { return nil } func (m *InboxModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if msg.String() == "ctrl+c" { return m, tea.Quit } case tea.WindowSizeMsg: m.list.SetWidth(msg.Width) } var cmd tea.Cmd m.list, cmd = m.list.Update(msg) return m, cmd } func (m InboxModel) View() string { if m.loading { return loadingStyle.Render("正在加载邮件...") } if m.err != nil { return errorStyle.Render(fmt.Sprintf("错误: %v", m.err)) } return m.list.View() } func (m *InboxModel) SetLoading(loading bool) { m.loading = loading } func (m *InboxModel) SetError(err error) { m.err = err } func (m InboxModel) SelectedEmail() *EmailItem { idx := m.list.Index() if idx >= 0 && idx < len(m.emails) { return &m.emails[idx] } return nil } var ( inboxTitleStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("86")). Bold(true) inboxNoItemsStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("241")). Padding(1, 2) loadingStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("86")). Padding(1, 2) errorStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). Padding(1, 2) ) type emailDelegate struct{} func (d emailDelegate) Height() int { return 1 } func (d emailDelegate) Spacing() int { return 0 } func (d emailDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } func (d emailDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) { email, ok := item.(EmailItem) if !ok { return } isSelected := index == m.Index() titleStyle := emailTitleStyle descStyle := emailDescStyle if isSelected { titleStyle = emailTitleStyleSelected descStyle = emailDescStyleSelected } fmt.Fprintf(w, "%s\n%s", titleStyle.Render(email.Title()), descStyle.Render(email.Description())) } var ( emailTitleStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("255")) emailTitleStyleSelected = lipgloss.NewStyle(). Foreground(lipgloss.Color("255")). Background(lipgloss.Color("68")) emailDescStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("241")) emailDescStyleSelected = lipgloss.NewStyle(). Foreground(lipgloss.Color("186")). Background(lipgloss.Color("68")) )