package main import ( "fmt" "io" "charm.land/bubbles/v2/list" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" ) type HistoryItem struct { id int64 from string to string subject string status string createdAt string sentAt string } func (h HistoryItem) FilterValue() string { return fmt.Sprintf("%s %s %s", h.from, h.to, h.subject) } func (h HistoryItem) Title() string { statusIcon := "📤" if h.status == "draft" { statusIcon = "📝" } return fmt.Sprintf("%s %s -> %s", statusIcon, h.from, h.to) } func (h HistoryItem) Description() string { return h.subject } type HistoryModel struct { list list.Model items []HistoryItem loading bool err error } func runHistory() error { m := NewHistoryModel() m.loading = true histories, err := GetEmailHistory("") if err != nil { m.err = err m.loading = false p := tea.NewProgram(m) if _, err := p.Run(); err != nil { return err } return nil } items := make([]list.Item, len(histories)) for i, h := range histories { status := "sent" if h.Status == "draft" { status = "draft" } sentAtStr := "" if h.SentAt != nil { sentAtStr = h.SentAt.Format("2006-01-02 15:04") } items[i] = HistoryItem{ id: h.ID, from: h.From, to: h.To, subject: h.Subject, status: status, createdAt: h.CreatedAt.Format("2006-01-02 15:04"), sentAt: sentAtStr, } } m.items = make([]HistoryItem, len(items)) for i := range items { m.items[i] = items[i].(HistoryItem) } m.list.SetItems(items) m.loading = false p := tea.NewProgram(m) if _, err := p.Run(); err != nil { return fmt.Errorf("failed to run history: %w", err) } return nil } func NewHistoryModel() *HistoryModel { l := list.New(nil, historyDelegate{}, 0, 10) l.Title = "发送历史" l.Styles.Title = historyTitleStyle l.Styles.NoItems = historyNoItemsStyle l.SetShowHelp(true) l.SetShowPagination(false) return &HistoryModel{ list: l, items: []HistoryItem{}, } } func (m *HistoryModel) Init() tea.Cmd { return nil } func (m *HistoryModel) 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 *HistoryModel) View() tea.View { if m.loading { return tea.NewView(historyLoadingStyle.Render("正在加载历史...")) } if m.err != nil { return tea.NewView(historyErrorStyle.Render(fmt.Sprintf("错误: %v", m.err))) } if len(m.items) == 0 { return tea.NewView(historyNoItemsStyle.Render("暂无发送历史")) } return tea.NewView(m.list.View()) } var ( historyTitleStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("86")). Bold(true) historyNoItemsStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("241")). Padding(1, 2) historyLoadingStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("86")). Padding(1, 2) historyErrorStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). Padding(1, 2) historyItemTitleStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("255")) historyItemDescStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("241")) ) type historyDelegate struct{} func (d historyDelegate) Height() int { return 1 } func (d historyDelegate) Spacing() int { return 0 } func (d historyDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } func (d historyDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) { h, ok := item.(HistoryItem) if !ok { return } isSelected := index == m.Index() titleStyle := historyItemTitleStyle descStyle := historyItemDescStyle if isSelected { titleStyle = titleStyle.Background(lipgloss.Color("68")) descStyle = descStyle.Background(lipgloss.Color("68")) } fmt.Fprintf(w, "%s\n%s", titleStyle.Render(h.Title()), descStyle.Render(h.Description())) }