2026-04-09 21:48:21 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
|
2026-04-10 04:41:22 +08:00
|
|
|
"charm.land/bubbles/v2/list"
|
|
|
|
|
tea "charm.land/bubbletea/v2"
|
|
|
|
|
"charm.land/lipgloss/v2"
|
2026-04-09 21:48:21 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 04:41:22 +08:00
|
|
|
func (m *HistoryModel) View() tea.View {
|
2026-04-09 21:48:21 +08:00
|
|
|
if m.loading {
|
2026-04-10 04:41:22 +08:00
|
|
|
return tea.NewView(historyLoadingStyle.Render("正在加载历史..."))
|
2026-04-09 21:48:21 +08:00
|
|
|
}
|
|
|
|
|
if m.err != nil {
|
2026-04-10 04:41:22 +08:00
|
|
|
return tea.NewView(historyErrorStyle.Render(fmt.Sprintf("错误: %v", m.err)))
|
2026-04-09 21:48:21 +08:00
|
|
|
}
|
|
|
|
|
if len(m.items) == 0 {
|
2026-04-10 04:41:22 +08:00
|
|
|
return tea.NewView(historyNoItemsStyle.Render("暂无发送历史"))
|
2026-04-09 21:48:21 +08:00
|
|
|
}
|
2026-04-10 04:41:22 +08:00
|
|
|
return tea.NewView(m.list.View())
|
2026-04-09 21:48:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()))
|
|
|
|
|
}
|