about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--commands/account/reply.go83
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--lib/msgid.go34
-rw-r--r--widgets/account.go12
-rw-r--r--widgets/compose.go14
6 files changed, 139 insertions, 7 deletions
diff --git a/commands/account/reply.go b/commands/account/reply.go
new file mode 100644
index 0000000..d0d65a5
--- /dev/null
+++ b/commands/account/reply.go
@@ -0,0 +1,83 @@
+package account
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/emersion/go-imap"
+
+	"git.sr.ht/~sircmpwn/aerc2/widgets"
+)
+
+func init() {
+	register("reply", Reply)
+}
+
+func Reply(aerc *widgets.Aerc, args []string) error {
+	if len(args) != 1 {
+		return errors.New("Usage: reply [-aq]")
+	}
+	// TODO: Reply all (w/ getopt)
+
+	acct := aerc.SelectedAccount()
+	msg := acct.Messages().Selected()
+	acct.Logger().Println("Replying to email " + msg.Envelope.MessageId)
+
+	var (
+		to     []string
+		cc     []string
+		toList []*imap.Address
+	)
+	if len(msg.Envelope.ReplyTo) != 0 {
+		toList = msg.Envelope.ReplyTo
+	} else {
+		toList = msg.Envelope.From
+	}
+	for _, addr := range toList {
+		if addr.PersonalName != "" {
+			to = append(to, fmt.Sprintf("%s <%s@%s>",
+				addr.PersonalName, addr.MailboxName, addr.HostName))
+		} else {
+			to = append(to, fmt.Sprintf("<%s@%s>",
+				addr.MailboxName, addr.HostName))
+		}
+	}
+	// TODO: Only if reply all
+	for _, addr := range msg.Envelope.Cc {
+		if addr.PersonalName != "" {
+			cc = append(cc, fmt.Sprintf("%s <%s@%s>",
+				addr.PersonalName, addr.MailboxName, addr.HostName))
+		} else {
+			cc = append(cc, fmt.Sprintf("<%s@%s>",
+				addr.MailboxName, addr.HostName))
+		}
+	}
+
+	subject := "Re: " + msg.Envelope.Subject
+
+	composer := widgets.NewComposer(
+		aerc.Config(), acct.AccountConfig(), acct.Worker()).
+		Defaults(map[string]string{
+			"To": strings.Join(to, ","),
+			"Cc": strings.Join(cc, ","),
+			"Subject": subject,
+			"In-Reply-To": msg.Envelope.MessageId,
+		}).
+		FocusTerminal()
+
+	tab := aerc.NewTab(composer, subject)
+
+	composer.OnSubjectChange(func(subject string) {
+		if subject == "" {
+			tab.Name = "New email"
+		} else {
+			tab.Name = subject
+		}
+		tab.Content.Invalidate()
+	})
+
+	return nil
+}
+
+
diff --git a/go.mod b/go.mod
index 0ddbcc2..f81ae62 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
 	github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
 	github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a
 	github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c // indirect
+	github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41
 	github.com/mattn/go-isatty v0.0.3
 	github.com/mattn/go-runewidth v0.0.2
 	github.com/miolini/datacounter v0.0.0-20171104152933-fd4e42a1d5e0
diff --git a/go.sum b/go.sum
index 30c58cc..8f497fe 100644
--- a/go.sum
+++ b/go.sum
@@ -40,6 +40,8 @@ github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a h1:vLFQnHOnCnmlySdpHAKF
 github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a/go.mod h1:Z5mDqe0fxyxn3W2yTxsBAOQqIrXADQIh02wrTnaRM38=
 github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c h1:b11Y3yxg40v2/9KUz76a4mSC1DMlgnPGAt+4pJSgmyU=
 github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
+github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41 h1:CVsnY46BCLkX9XOhALJ/S7yb9ayc4eqjXSXO3tyB66A=
+github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
 github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791 h1:PfHMsLQJwoc0ccjK0sam6J0wQo4s8mOuAo2yQGw+T2U=
diff --git a/lib/msgid.go b/lib/msgid.go
new file mode 100644
index 0000000..8282d1d
--- /dev/null
+++ b/lib/msgid.go
@@ -0,0 +1,34 @@
+package lib
+
+// TODO: Remove this pending merge into github.com/emersion/go-message
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"math/rand"
+	"os"
+	"time"
+
+	"github.com/martinlindhe/base36"
+)
+
+// Generates an RFC 2822-complaint Message-Id based on the informational draft
+// "Recommendations for generating Message IDs", for lack of a better
+// authoritative source.
+func GenerateMessageId() string {
+	var (
+		now   bytes.Buffer
+		nonce bytes.Buffer
+	)
+	binary.Write(&now, binary.BigEndian, time.Now().UnixNano())
+	binary.Write(&nonce, binary.BigEndian, rand.Uint64())
+	hostname, err := os.Hostname()
+	if err != nil {
+		hostname = "localhost"
+	}
+	return fmt.Sprintf("<%s.%s@%s>",
+		base36.EncodeBytes(now.Bytes()),
+		base36.EncodeBytes(nonce.Bytes()),
+		hostname)
+}
diff --git a/widgets/account.go b/widgets/account.go
index c252e38..ab32f5d 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -84,6 +84,14 @@ func (acct *AccountView) AccountConfig() *config.AccountConfig {
 	return acct.acct
 }
 
+func (acct *AccountView) Worker() *types.Worker {
+	return acct.worker
+}
+
+func (acct *AccountView) Logger() *log.Logger {
+	return acct.logger
+}
+
 func (acct *AccountView) Name() string {
 	return acct.acct.Name
 }
@@ -110,10 +118,6 @@ func (acct *AccountView) Focus(focus bool) {
 	// TODO: Unfocus children I guess
 }
 
-func (acct *AccountView) Worker() *types.Worker {
-	return acct.worker
-}
-
 func (acct *AccountView) connected(msg types.WorkerMessage) {
 	switch msg := msg.(type) {
 	case *types.Done:
diff --git a/widgets/compose.go b/widgets/compose.go
index 2359cad..02a9d0c 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -14,6 +14,7 @@ import (
 	"github.com/mattn/go-runewidth"
 
 	"git.sr.ht/~sircmpwn/aerc2/config"
+	"git.sr.ht/~sircmpwn/aerc2/lib"
 	"git.sr.ht/~sircmpwn/aerc2/lib/ui"
 	"git.sr.ht/~sircmpwn/aerc2/worker/types"
 )
@@ -123,6 +124,13 @@ func (c *Composer) Defaults(defaults map[string]string) *Composer {
 	return c
 }
 
+func (c *Composer) FocusTerminal() *Composer {
+	c.focusable[c.focused].Focus(false)
+	c.focused = 3
+	c.focusable[c.focused].Focus(true)
+	return c
+}
+
 func (c *Composer) OnSubjectChange(fn func(subject string)) {
 	c.headers.subject.OnChange(func() {
 		fn(c.headers.subject.input.String())
@@ -197,9 +205,9 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) {
 		c.email.Seek(0, os.SEEK_SET)
 	}
 	// Update headers
-	// TODO: Custom header fields
 	mhdr := (*message.Header)(&header.Header)
 	mhdr.SetContentType("text/plain", map[string]string{"charset": "UTF-8"})
+	mhdr.SetText("Message-Id", lib.GenerateMessageId())
 	if subject, _ := header.Subject(); subject == "" {
 		header.SetSubject(c.headers.subject.input.String())
 	}
@@ -228,14 +236,14 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) {
 			rcpts = append(rcpts, addr.Address)
 		}
 	}
+	// TODO: Add cc, bcc to rcpts
 	// Merge in additional headers
 	txthdr := mhdr.Header
 	for key, value := range c.defaults {
-		if !txthdr.Has(key) {
+		if !txthdr.Has(key) && value != "" {
 			mhdr.SetText(key, value)
 		}
 	}
-	// TODO: Add cc, bcc to rcpts
 	return &header, rcpts, nil
 }