diff --git a/commands/account/compose.go b/commands/account/compose.go index 42d51d5..b33acf5 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -31,7 +31,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error { } acct := aerc.SelectedAccount() - composer, err := widgets.NewComposer(aerc, acct + composer, err := widgets.NewComposer(aerc, acct, aerc.Config(), acct.AccountConfig(), acct.Worker(), template, nil, models.OriginalMail{}) if err != nil { diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go index 44aa411..60c9df1 100644 --- a/commands/compose/postpone.go +++ b/commands/compose/postpone.go @@ -5,10 +5,10 @@ import ( "io/ioutil" "time" - "github.com/emersion/go-imap" "github.com/miolini/datacounter" "github.com/pkg/errors" + "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/worker/types" ) @@ -79,7 +79,7 @@ func (Postpone) Execute(aerc *widgets.Aerc, args []string) error { r, w := io.Pipe() worker.PostAction(&types.AppendMessage{ Destination: config.Postpone, - Flags: []string{imap.SeenFlag}, + Flags: []models.Flag{models.SeenFlag}, Date: time.Now(), Reader: r, Length: int(nbytes), diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 833eb9f..35a65d8 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -78,7 +78,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error { original.Date = msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM") } - composer, err := widgets.NewComposer(aerc, aerc.Config(), acct.AccountConfig(), + composer, err := widgets.NewComposer(aerc, acct, aerc.Config(), acct.AccountConfig(), acct.Worker(), template, defaults, original) if err != nil { aerc.PushError("Error: " + err.Error()) diff --git a/commands/msg/recall.go b/commands/msg/recall.go new file mode 100644 index 0000000..c2f887a --- /dev/null +++ b/commands/msg/recall.go @@ -0,0 +1,141 @@ +package msg + +import ( + "io" + + "github.com/emersion/go-message" + _ "github.com/emersion/go-message/charset" + "github.com/emersion/go-message/mail" + "github.com/pkg/errors" + + "git.sr.ht/~sircmpwn/aerc/models" + "git.sr.ht/~sircmpwn/aerc/widgets" + "git.sr.ht/~sircmpwn/aerc/worker/types" +) + +type Recall struct{} + +func init() { + register(Recall{}) +} + +func (Recall) Aliases() []string { + return []string{"recall"} +} + +func (Recall) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (Recall) Execute(aerc *widgets.Aerc, args []string) error { + if len(args) != 1 { + return errors.New("Usage: recall") + } + + widget := aerc.SelectedTab().(widgets.ProvidesMessage) + acct := widget.SelectedAccount() + if acct == nil { + return errors.New("No account selected") + } + if acct.SelectedDirectory() != acct.AccountConfig().Postpone { + return errors.New("Can only recall from the postpone directory: " + + acct.AccountConfig().Postpone) + } + store := widget.Store() + if store == nil { + return errors.New("Cannot perform action. Messages still loading") + } + + msgInfo, err := widget.SelectedMessage() + if err != nil { + return errors.Wrap(err, "Recall failed") + } + acct.Logger().Println("Recalling message " + msgInfo.Envelope.MessageId) + + // copy the headers to the defaults map for addition to the composition + defaults := make(map[string]string) + headerFields := msgInfo.RFC822Headers.Fields() + for headerFields.Next() { + defaults[headerFields.Key()] = headerFields.Value() + } + + composer, err := widgets.NewComposer(aerc, acct, aerc.Config(), + acct.AccountConfig(), acct.Worker(), "", defaults, models.OriginalMail{}) + if err != nil { + return errors.Wrap(err, "Cannot open a new composer") + } + + // focus the terminal since the header fields are likely already done + composer.FocusTerminal() + + addTab := func() { + subject := msgInfo.Envelope.Subject + if subject == "" { + subject = "Recalled email" + } + tab := aerc.NewTab(composer, subject) + composer.OnHeaderChange("Subject", func(subject string) { + if subject == "" { + tab.Name = "New email" + } else { + tab.Name = subject + } + tab.Content.Invalidate() + }) + composer.OnClose(func(composer *widgets.Composer) { + worker := composer.Worker() + uids := []uint32{msgInfo.Uid} + + worker.PostAction(&types.DeleteMessages{ + Uids: uids, + }, func(msg types.WorkerMessage) { + switch msg := msg.(type) { + case *types.Error: + aerc.PushError(" " + msg.Error.Error()) + composer.Close() + } + }) + + return + }) + } + + // find the main body part and add it to the editor + // TODO: copy all parts of the message over? + var ( + path []int + part *models.BodyStructure + ) + if len(msgInfo.BodyStructure.Parts) != 0 { + part, path = findPlaintext(msgInfo.BodyStructure, path) + } + if part == nil { + part = msgInfo.BodyStructure + path = []int{1} + } + + store.FetchBodyPart(msgInfo.Uid, part, path, func(reader io.Reader) { + header := message.Header{} + header.SetText( + "Content-Transfer-Encoding", part.Encoding) + header.SetContentType(part.MIMEType, part.Params) + header.SetText("Content-Description", part.Description) + entity, err := message.New(header, reader) + if err != nil { + // TODO: Do something with the error + addTab() + return + } + mreader := mail.NewReader(entity) + part, err := mreader.NextPart() + if err != nil { + // TODO: Do something with the error + addTab() + return + } + composer.SetContents(part.Body) + addTab() + }) + + return nil +} diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index 230361d..ec3f171 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -103,6 +103,10 @@ message list, the message in the message viewer, etc). *delete* Deletes the selected message. +*recall* + Opens the selected message for re-editing. Messages can only be + recalled from the postpone directory. + *forward* [-A] [address...] Opens the composer to forward the selected message to another recipient. diff --git a/widgets/account.go b/widgets/account.go index a854fb6..31384a5 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -200,6 +200,10 @@ func (acct *AccountView) SelectedAccount() *AccountView { return acct } +func (acct *AccountView) SelectedDirectory() string { + return acct.dirlist.Selected() +} + func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) { if len(acct.msglist.Store().Uids()) == 0 { return nil, errors.New("no message selected") diff --git a/widgets/compose.go b/widgets/compose.go index 66877cc..4281941 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -59,7 +59,7 @@ type Composer struct { } func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig, - acct *config.AccountConfig, worker *types.Worker, template string, + acctConfig *config.AccountConfig, worker *types.Worker, template string, defaults map[string]string, original models.OriginalMail) (*Composer, error) { if defaults == nil {