feat: 收件箱功能新增按回车查看详情面板

- 添加邮件详情面板显示(主题、发件人、收件人、抄送、账户、时间、正文)
- 优化邮件列表卡片样式,增加选中高亮效果
- 窗口宽度 >= 80 时启用双面板布局,左侧列表右侧详情
- 简化依赖包,从 charm.land 使用统一导入路径
- 删除未使用的 golangci/goreleaser 配置文件
This commit is contained in:
2026-04-10 04:41:22 +08:00
parent 52c5eb5ae8
commit c9b77feabe
14 changed files with 707 additions and 238 deletions

View File

@@ -5,14 +5,14 @@ import (
"strings"
"time"
"github.com/charmbracelet/bubbles/filepicker"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"charm.land/bubbles/v2/filepicker"
"charm.land/bubbles/v2/help"
"charm.land/bubbles/v2/key"
"charm.land/bubbles/v2/list"
"charm.land/bubbles/v2/spinner"
"charm.land/bubbles/v2/textarea"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/exp/ordered"
"github.com/resendlabs/resend-go"
)
@@ -89,68 +89,32 @@ func NewModel(defaults resend.SendEmailRequest, deliveryMethod DeliveryMethod) M
from := textinput.New()
from.Prompt = "From "
from.Placeholder = "me@example.com"
from.PromptStyle = labelStyle.Copy()
from.PromptStyle = labelStyle
from.TextStyle = textStyle
from.Cursor.Style = cursorStyle
from.PlaceholderStyle = placeholderStyle
from.SetValue(defaults.From)
to := textinput.New()
to.Prompt = "To "
to.PromptStyle = labelStyle.Copy()
to.Cursor.Style = cursorStyle
to.PlaceholderStyle = placeholderStyle
to.TextStyle = textStyle
to.Placeholder = "you@example.com"
to.SetValue(strings.Join(defaults.To, ToSeparator))
cc := textinput.New()
cc.Prompt = "Cc "
cc.PromptStyle = labelStyle.Copy()
cc.Cursor.Style = cursorStyle
cc.PlaceholderStyle = placeholderStyle
cc.TextStyle = textStyle
cc.Placeholder = "cc@example.com"
cc.SetValue(strings.Join(defaults.Cc, ToSeparator))
bcc := textinput.New()
bcc.Prompt = "Bcc "
bcc.PromptStyle = labelStyle.Copy()
bcc.Cursor.Style = cursorStyle
bcc.PlaceholderStyle = placeholderStyle
bcc.TextStyle = textStyle
bcc.Placeholder = "bcc@example.com"
bcc.SetValue(strings.Join(defaults.Bcc, ToSeparator))
subject := textinput.New()
subject.Prompt = "Subject "
subject.PromptStyle = labelStyle.Copy()
subject.Cursor.Style = cursorStyle
subject.PlaceholderStyle = placeholderStyle
subject.TextStyle = textStyle
subject.Placeholder = "Hello!"
subject.SetValue(defaults.Subject)
body := textarea.New()
body.Placeholder = "# Email"
body.ShowLineNumbers = false
body.FocusedStyle.CursorLine = activeTextStyle
body.FocusedStyle.Prompt = activeLabelStyle
body.FocusedStyle.Text = activeTextStyle
body.FocusedStyle.Placeholder = placeholderStyle
body.BlurredStyle.CursorLine = textStyle
body.BlurredStyle.Prompt = labelStyle
body.BlurredStyle.Text = textStyle
body.BlurredStyle.Placeholder = placeholderStyle
body.Cursor.Style = cursorStyle
body.CharLimit = 4000
body.SetValue(defaults.Text)
// Adjust for signature (if none, this is a no-op)
body.CursorUp()
body.CursorUp()
body.CharLimit = 4000
body.Blur()
// Decide which input to focus.
@@ -170,9 +134,6 @@ func NewModel(defaults resend.SendEmailRequest, deliveryMethod DeliveryMethod) M
attachments.DisableQuitKeybindings()
attachments.SetShowTitle(true)
attachments.Title = "Attachments"
attachments.Styles.Title = labelStyle
attachments.Styles.TitleBar = labelStyle
attachments.Styles.NoItems = placeholderStyle
attachments.SetShowHelp(false)
attachments.SetShowStatusBar(false)
attachments.SetStatusBarItemName("attachment", "attachments")
@@ -186,7 +147,6 @@ func NewModel(defaults resend.SendEmailRequest, deliveryMethod DeliveryMethod) M
picker.CurrentDirectory, _ = os.UserHomeDir()
loadingSpinner := spinner.New()
loadingSpinner.Style = activeLabelStyle
loadingSpinner.Spinner = spinner.Dot
m := Model{
@@ -213,9 +173,7 @@ func NewModel(defaults resend.SendEmailRequest, deliveryMethod DeliveryMethod) M
// Init initializes the model.
func (m Model) Init() tea.Cmd {
return tea.Batch(
m.From.Cursor.BlinkCmd(),
)
return nil
}
type clearErrMsg struct{}
@@ -370,70 +328,46 @@ func (m *Model) blurInputs() {
m.Cc.Blur()
m.Bcc.Blur()
}
m.From.PromptStyle = labelStyle
m.To.PromptStyle = labelStyle
if m.showCc {
m.Cc.PromptStyle = labelStyle
m.Cc.TextStyle = textStyle
m.Bcc.PromptStyle = labelStyle
m.Bcc.TextStyle = textStyle
}
m.Subject.PromptStyle = labelStyle
m.From.TextStyle = textStyle
m.To.TextStyle = textStyle
m.Subject.TextStyle = textStyle
m.Attachments.Styles.Title = labelStyle
m.Attachments.SetDelegate(attachmentDelegate{false})
}
func (m *Model) focusActiveInput() {
switch m.state {
case editingFrom:
m.From.PromptStyle = activeLabelStyle
m.From.TextStyle = activeTextStyle
m.From.Focus()
m.From.CursorEnd()
case editingTo:
m.To.PromptStyle = activeLabelStyle
m.To.TextStyle = activeTextStyle
m.To.Focus()
m.To.CursorEnd()
case editingCc:
m.Cc.PromptStyle = activeLabelStyle
m.Cc.TextStyle = activeTextStyle
m.Cc.Focus()
m.Cc.CursorEnd()
case editingBcc:
m.Bcc.PromptStyle = activeLabelStyle
m.Bcc.TextStyle = activeTextStyle
m.Bcc.Focus()
m.Bcc.CursorEnd()
case editingSubject:
m.Subject.PromptStyle = activeLabelStyle
m.Subject.TextStyle = activeTextStyle
m.Subject.Focus()
m.Subject.CursorEnd()
case editingBody:
m.Body.Focus()
m.Body.CursorEnd()
case editingAttachments:
m.Attachments.Styles.Title = activeLabelStyle
m.Attachments.SetDelegate(attachmentDelegate{true})
}
}
// View displays the application.
func (m Model) View() string {
func (m Model) View() tea.View {
if m.quitting {
return ""
return tea.NewView("")
}
switch m.state {
case pickingFile:
return "\n" + activeLabelStyle.Render("Attachments") + " " + commentStyle.Render(m.filepicker.CurrentDirectory) +
"\n\n" + m.filepicker.View()
return tea.NewView("\n" + activeLabelStyle.Render("Attachments") + " " + commentStyle.Render(m.filepicker.CurrentDirectory) +
"\n\n" + m.filepicker.View())
case sendingEmail:
return "\n " + m.loadingSpinner.View() + "Sending email"
return tea.NewView("\n " + m.loadingSpinner.View() + "Sending email")
}
var s strings.Builder
@@ -469,5 +403,5 @@ func (m Model) View() string {
s.WriteString(errorStyle.Render(m.err.Error()))
}
return paddedStyle.Render(s.String())
return tea.NewView(paddedStyle.Render(s.String()))
}