diff --git a/config/default_keybinds.json b/config/default_keybinds.json index 4c13dcee..59b9e368 100644 --- a/config/default_keybinds.json +++ b/config/default_keybinds.json @@ -26,7 +26,8 @@ "rsvp_accept": "1", "rsvp_decline": "2", "rsvp_tentative": "3", - "focus_attachments": "tab" + "focus_attachments": "tab", + "open_html_browser": "o" }, "composer": { "external_editor": "ctrl+e", diff --git a/config/keybinds.go b/config/keybinds.go index 7853c6ac..63a68d0c 100644 --- a/config/keybinds.go +++ b/config/keybinds.go @@ -56,6 +56,7 @@ type EmailKeys struct { RsvpDecline string `json:"rsvp_decline"` RsvpTentative string `json:"rsvp_tentative"` FocusAttachments string `json:"focus_attachments"` + OpenHTMLBrowser string `json:"open_html_browser"` } type ComposerKeys struct { diff --git a/main.go b/main.go index e8c62cac..434fd479 100644 --- a/main.go +++ b/main.go @@ -1706,6 +1706,8 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo m.syncPluginKeyBindings() return m, m.current.Init() + case tui.OpenHTMLEmailMsg: + return m, openHTMLInBrowser(msg.Email.Body) case tui.OpenEditorMsg: composer, ok := m.current.(*tui.Composer) if !ok { @@ -3165,6 +3167,57 @@ func openExternalEditor(body string) tea.Cmd { }) } +// openHTMLInBrowser writes the HTML body to a temp file and opens it in the default browser. +func openHTMLInBrowser(htmlBody string) tea.Cmd { + return func() tea.Msg { + tmpFile, err := os.CreateTemp("", "matcha-*.html") + if err != nil { + return nil + } + tmpPath := tmpFile.Name() + + if _, err := tmpFile.WriteString(htmlBody); err != nil { + _ = tmpFile.Close() + _ = os.Remove(tmpPath) + return nil + } + if err := tmpFile.Close(); err != nil { + _ = os.Remove(tmpPath) + return nil + } + + // Determine the command to open the browser based on the OS + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + var cmd *exec.Cmd + switch runtime.GOOS { + case goosDarwin: // macOS + cmd = exec.CommandContext(ctx, "open", tmpPath) + case "linux": + cmd = exec.CommandContext(ctx, "xdg-open", tmpPath) + case "windows": + cmd = exec.CommandContext(ctx, "cmd", "/c", "start", tmpPath) + default: + _ = os.Remove(tmpPath) + return nil + } + + // Run the command asynchronously without waiting for it to complete + // so the temp file isn't deleted before the browser opens it + if err := cmd.Start(); err != nil { + _ = os.Remove(tmpPath) + return nil + } + + // Schedule cleanup of the temp file after a delay to allow the browser to read it + time.AfterFunc(30*time.Second, func() { + _ = os.Remove(tmpPath) + }) + + return nil + } +} + // --- IDLE command --- // listenForIdleUpdates blocks until an IDLE update arrives, then returns it as a tea.Msg. diff --git a/tui/email_view.go b/tui/email_view.go index e764d6aa..660c9311 100644 --- a/tui/email_view.go +++ b/tui/email_view.go @@ -194,7 +194,7 @@ func (m *EmailView) Init() tea.Cmd { return nil } -func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:gocyclo var cmd tea.Cmd cmds := make([]tea.Cmd, 0, 1) @@ -312,6 +312,12 @@ func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if len(m.email.Attachments) > 0 { m.focusOnAttachments = true } + case kb.Email.OpenHTMLBrowser: + if m.email.BodyMIMEType == "text/html" && len(m.email.Body) > 0 { + // Clear Kitty graphics before opening browser + ClearKittyGraphics() + return m, func() tea.Msg { return OpenHTMLEmailMsg{Email: m.email} } + } } } case tea.WindowSizeMsg: @@ -386,8 +392,11 @@ func (m *EmailView) View() tea.View { } else { var shortcuts strings.Builder shortcuts.WriteString("\uf112 r: reply • \uf064 f: forward • \uea81 d: delete • \uea98 a: archive • \uf435 tab: focus attachments • \ueb06 esc: back to inbox") + if m.email.BodyMIMEType == "text/html" && len(m.email.Body) > 0 { + shortcuts.WriteString(" • \uf303 o: open in browser") + } if view.ImageProtocolSupported() { - shortcuts.WriteString("• \uf03e i: toggle images") + shortcuts.WriteString(" • \uf03e i: toggle images") } for _, pk := range m.pluginKeyBindings { shortcuts.WriteString(" • ") diff --git a/tui/messages.go b/tui/messages.go index 26bc5090..62a74dca 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -184,6 +184,10 @@ type ForwardEmailMsg struct { Email fetcher.Email } +type OpenHTMLEmailMsg struct { + Email fetcher.Email +} + type SetComposerCursorToStartMsg struct{} type GoToFilePickerMsg struct{}