@@ -9,6 +9,7 @@ import ( | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"time" | "time" | ||||
nettextproto "net/textproto" | |||||
"github.com/dustin/go-humanize" | "github.com/dustin/go-humanize" | ||||
"github.com/emersion/go-imap" | "github.com/emersion/go-imap" | ||||
@@ -570,20 +571,20 @@ func markMessageAnswered(conn *imapclient.Client, mboxName string, uid uint32) e | |||||
return conn.UidStore(seqSet, item, flags, nil) | return conn.UidStore(seqSet, item, flags, nil) | ||||
} | } | ||||
func appendMessage(c *imapclient.Client, msg *OutgoingMessage, mboxType mailboxType) (saved bool, err error) { | |||||
func appendMessage(c *imapclient.Client, msg *OutgoingMessage, mboxType mailboxType) (*MailboxInfo, uint32, error) { | |||||
mbox, err := getMailboxByType(c, mboxType) | mbox, err := getMailboxByType(c, mboxType) | ||||
if err != nil { | if err != nil { | ||||
return false, err | |||||
return nil, 0, err | |||||
} | } | ||||
if mbox == nil { | if mbox == nil { | ||||
return false, nil | |||||
return nil, 0, fmt.Errorf("Unable to resolve mailbox") | |||||
} | } | ||||
// IMAP needs to know in advance the final size of the message, so | // IMAP needs to know in advance the final size of the message, so | ||||
// there's no way around storing it in a buffer here. | // there's no way around storing it in a buffer here. | ||||
var buf bytes.Buffer | var buf bytes.Buffer | ||||
if err := msg.WriteTo(&buf); err != nil { | if err := msg.WriteTo(&buf); err != nil { | ||||
return false, err | |||||
return nil, 0, err | |||||
} | } | ||||
flags := []string{imap.SeenFlag} | flags := []string{imap.SeenFlag} | ||||
@@ -591,10 +592,20 @@ func appendMessage(c *imapclient.Client, msg *OutgoingMessage, mboxType mailboxT | |||||
flags = append(flags, imap.DraftFlag) | flags = append(flags, imap.DraftFlag) | ||||
} | } | ||||
if err := c.Append(mbox.Name, flags, time.Now(), &buf); err != nil { | if err := c.Append(mbox.Name, flags, time.Now(), &buf); err != nil { | ||||
return false, err | |||||
return nil, 0, err | |||||
} | |||||
criteria := &imap.SearchCriteria{ | |||||
Header: make(nettextproto.MIMEHeader), | |||||
} | |||||
criteria.Header.Add("Message-Id", msg.MessageID) | |||||
if uids, err := c.UidSearch(criteria); err != nil { | |||||
return nil, 0, err | |||||
} else { | |||||
if len(uids) != 1 { | |||||
panic(fmt.Errorf("Duplicate message ID")) | |||||
} | |||||
return mbox, uids[0], nil | |||||
} | } | ||||
return true, nil | |||||
} | } | ||||
func deleteMessage(c *imapclient.Client, mboxName string, uid uint32) error { | func deleteMessage(c *imapclient.Client, mboxName string, uid uint32) error { | ||||
@@ -507,7 +507,7 @@ func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti | |||||
} | } | ||||
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { | err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { | ||||
if _, err := appendMessage(c, msg, mailboxSent); err != nil { | |||||
if _, _, err := appendMessage(c, msg, mailboxSent); err != nil { | |||||
return err | return err | ||||
} | } | ||||
if draft := options.Draft; draft != nil { | if draft := options.Draft; draft != nil { | ||||
@@ -620,14 +620,15 @@ func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti | |||||
} | } | ||||
if saveAsDraft { | if saveAsDraft { | ||||
var ( | |||||
drafts *MailboxInfo | |||||
uid uint32 | |||||
) | |||||
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { | err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { | ||||
copied, err := appendMessage(c, msg, mailboxDrafts) | |||||
drafts, uid, err = appendMessage(c, msg, mailboxDrafts) | |||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } | ||||
if !copied { | |||||
return fmt.Errorf("no Draft mailbox found") | |||||
} | |||||
if draft := options.Draft; draft != nil { | if draft := options.Draft; draft != nil { | ||||
if err := deleteMessage(c, draft.Mailbox, draft.Uid); err != nil { | if err := deleteMessage(c, draft.Mailbox, draft.Uid); err != nil { | ||||
return err | return err | ||||
@@ -638,7 +639,8 @@ func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti | |||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("failed to save message to Draft mailbox: %v", err) | return fmt.Errorf("failed to save message to Draft mailbox: %v", err) | ||||
} | } | ||||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | |||||
return ctx.Redirect(http.StatusFound, fmt.Sprintf( | |||||
"/message/%s/%d/edit?part=1", drafts.Name, uid)) | |||||
} else { | } else { | ||||
return submitCompose(ctx, msg, options) | return submitCompose(ctx, msg, options) | ||||
} | } | ||||
@@ -1,3 +1,6 @@ | |||||
const sendButton = document.getElementById("send-button"), | |||||
saveButton = document.getElementById("save-button"); | |||||
const composeForm = document.getElementById("compose-form"); | const composeForm = document.getElementById("compose-form"); | ||||
const sendProgress = document.getElementById("send-progress"); | const sendProgress = document.getElementById("send-progress"); | ||||
composeForm.addEventListener("submit", ev => { | composeForm.addEventListener("submit", ev => { | ||||
@@ -6,6 +9,10 @@ composeForm.addEventListener("submit", ev => { | |||||
sendProgress.style.display = 'flex'; | sendProgress.style.display = 'flex'; | ||||
}); | }); | ||||
saveButton.addEventListener("click", ev => { | |||||
sendProgress.querySelector(".info").innerText = "Saving draft..."; | |||||
}); | |||||
let attachments = []; | let attachments = []; | ||||
const headers = document.querySelector(".create-update .headers"); | const headers = document.querySelector(".create-update .headers"); | ||||
@@ -41,9 +48,6 @@ function dragNOP(e) { | |||||
e.preventDefault(); | e.preventDefault(); | ||||
} | } | ||||
const sendButton = document.getElementById("send-button"), | |||||
saveButton = document.getElementById("save-button"); | |||||
const attachmentUUIDsNode = document.getElementById("attachment-uuids"); | const attachmentUUIDsNode = document.getElementById("attachment-uuids"); | ||||
function updateState() { | function updateState() { | ||||
let complete = true; | let complete = true; | ||||
@@ -70,7 +70,7 @@ | |||||
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) | License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) | ||||
--> | --> | ||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M288 39.056v16.659c0 10.804 7.281 20.159 17.686 23.066C383.204 100.434 440 171.518 440 256c0 101.689-82.295 184-184 184-101.689 0-184-82.295-184-184 0-84.47 56.786-155.564 134.312-177.219C216.719 75.874 224 66.517 224 55.712V39.064c0-15.709-14.834-27.153-30.046-23.234C86.603 43.482 7.394 141.206 8.003 257.332c.72 137.052 111.477 246.956 248.531 246.667C393.255 503.711 504 392.788 504 256c0-115.633-79.14-212.779-186.211-240.236C302.678 11.889 288 23.456 288 39.056z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M288 39.056v16.659c0 10.804 7.281 20.159 17.686 23.066C383.204 100.434 440 171.518 440 256c0 101.689-82.295 184-184 184-101.689 0-184-82.295-184-184 0-84.47 56.786-155.564 134.312-177.219C216.719 75.874 224 66.517 224 55.712V39.064c0-15.709-14.834-27.153-30.046-23.234C86.603 43.482 7.394 141.206 8.003 257.332c.72 137.052 111.477 246.956 248.531 246.667C393.255 503.711 504 392.788 504 256c0-115.633-79.14-212.779-186.211-240.236C302.678 11.889 288 23.456 288 39.056z"/></svg> | ||||
<span>Sending message...</span> | |||||
<span class="info">Sending message...</span> | |||||
</div> | </div> | ||||
</div> | </div> | ||||