Use header-only IMAP lists

This commit is contained in:
DonVoo 2026-07-14 18:23:31 +02:00
parent a52963c380
commit a17d2c8a2c
5 changed files with 456 additions and 184 deletions

View file

@ -14,8 +14,11 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
)
const mailboxListLimit = 200
func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/login", loginHandler)
@ -394,13 +397,13 @@ func sourceMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return
}
messages, err := fetchSourceFolderMessages(account, folder)
headers, err := fetchSourceFolderHeaders(account, folder)
if err != nil {
fmt.Fprintf(w, `<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return
}
renderSourceMailboxMessageList(w, account, folder, messages)
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderSourceMailboxRead(account, folder, 0, messages))
renderSourceMailboxMessageList(w, account, folder, headers)
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderSourceMailboxRead(account, folder, firstHeaderUID(headers)))
}
func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
@ -409,23 +412,19 @@ func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
}
accountName := strings.TrimSpace(r.URL.Query().Get("source"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, "bad index", http.StatusBadRequest)
return
}
uid := parseUIDParam(r)
account, err := GetAccount(accountName)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
messages, err := fetchSourceFolderMessages(account, folder)
raw, err := fetchSourceMailboxMessage(account, folder, uid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, renderSourceMailboxRead(account, folder, index, messages))
fmt.Fprint(w, renderSourceMailboxReadMessage(account, folder, raw))
}
func targetMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
@ -444,13 +443,13 @@ func targetMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return
}
messages, err := fetchTargetFolderMessages(account, folder)
headers, err := fetchTargetFolderHeaders(account, folder)
if err != nil {
fmt.Fprintf(w, `<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return
}
renderTargetMailboxMessageList(w, account, folder, messages)
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderTargetMailboxRead(account, folder, 0, messages))
renderTargetMailboxMessageList(w, account, folder, headers)
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderTargetMailboxRead(account, folder, firstHeaderUID(headers)))
}
func targetMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
@ -459,23 +458,19 @@ func targetMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
}
accountName := strings.TrimSpace(r.URL.Query().Get("target"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, "bad index", http.StatusBadRequest)
return
}
uid := parseUIDParam(r)
account, err := GetAccount(accountName)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
messages, err := fetchTargetFolderMessages(account, folder)
raw, err := fetchTargetMailboxMessage(account, folder, uid)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, renderTargetMailboxRead(account, folder, index, messages))
fmt.Fprint(w, renderTargetMailboxReadMessage(account, folder, raw))
}
func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
@ -999,7 +994,7 @@ func transferMessagesHandler(w http.ResponseWriter, r *http.Request) {
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, renderTransferMessageList(side, kind, value, folder))
fmt.Fprintf(w, `<div id="transfer-preview" hx-swap-oob="innerHTML">%s</div>`, renderTransferPreview(kind, value, folder, 0))
fmt.Fprintf(w, `<div id="transfer-preview" hx-swap-oob="innerHTML">%s</div>`, renderTransferPreview(kind, value, folder, 0, 0))
}
func transferMessageHandler(w http.ResponseWriter, r *http.Request) {
@ -1009,13 +1004,10 @@ func transferMessageHandler(w http.ResponseWriter, r *http.Request) {
kind := strings.TrimSpace(r.URL.Query().Get("kind"))
value := strings.TrimSpace(r.URL.Query().Get("value"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, "bad index", http.StatusBadRequest)
return
}
index, _ := strconv.Atoi(r.URL.Query().Get("index"))
uid := parseUIDParam(r)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, renderTransferPreview(kind, value, folder, index))
fmt.Fprint(w, renderTransferPreview(kind, value, folder, index, uid))
}
func transferSide(value string) string {
@ -1183,12 +1175,12 @@ func renderInitialTransferList(side, kind string, accounts []Account, archives [
func renderInitialTransferPreview(leftKind, leftValue, rightKind, rightValue string) string {
if leftValue != "" {
if folder, err := firstTransferFolder(leftKind, leftValue); err == nil {
return renderTransferPreview(leftKind, leftValue, folder, 0)
return renderTransferPreview(leftKind, leftValue, folder, 0, 0)
}
}
if rightValue != "" {
if folder, err := firstTransferFolder(rightKind, rightValue); err == nil {
return renderTransferPreview(rightKind, rightValue, folder, 0)
return renderTransferPreview(rightKind, rightValue, folder, 0, 0)
}
}
return renderEmptyPreviewPane("Nachricht links oder rechts waehlen.")
@ -1271,56 +1263,45 @@ func renderTransferMessageList(side, kind, value, folder string) string {
fmt.Fprintf(&b, `<div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return b.String()
}
messages, err := fetchTargetFolderMessages(account, folder)
messages, err := fetchTargetFolderHeaders(account, folder)
if err != nil {
fmt.Fprintf(&b, `<div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return b.String()
}
renderTransferRawMessages(&b, side, kind, value, folder, messages)
renderTransferHeaders(&b, side, kind, value, folder, messages)
default:
account, err := GetAccount(value)
if err != nil {
fmt.Fprintf(&b, `<div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return b.String()
}
messages, err := fetchSourceFolderMessages(account, folder)
messages, err := fetchSourceFolderHeaders(account, folder)
if err != nil {
fmt.Fprintf(&b, `<div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
return b.String()
}
renderTransferRawMessages(&b, side, kind, value, folder, messages)
renderTransferHeaders(&b, side, kind, value, folder, messages)
}
return b.String()
}
func renderTransferRawMessages(b *strings.Builder, side, kind, value, folder string, messages []RawMessage) {
for i, raw := range messages {
subject := "(ohne Betreff)"
from := "(ohne Absender)"
date := ""
if msg, err := mail.ReadMessage(bytes.NewReader(raw.Body)); err == nil {
if v := decodeHeader(msg.Header.Get("Subject")); v != "" {
subject = v
}
if v := decodeHeader(msg.Header.Get("From")); v != "" {
from = v
}
date = decodeHeader(msg.Header.Get("Date"))
}
func renderTransferHeaders(b *strings.Builder, side, kind, value, folder string, messages []MessageHeader) {
for i, header := range messages {
subject, from, date := headerListFields(header)
active := ""
if i == 0 {
active = " active"
}
fmt.Fprintf(b, `<a class="msg-row%s" href="#" hx-get="/transfer/message?side=%s&amp;kind=%s&amp;value=%s&amp;folder=%s&amp;index=%d" hx-target="#transfer-preview" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
active, side, html.EscapeString(kind), urlEsc(value), urlEsc(folder), i, html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
fmt.Fprintf(b, `<a class="msg-row%s" href="#" hx-get="/transfer/message?side=%s&amp;kind=%s&amp;value=%s&amp;folder=%s&amp;uid=%d" hx-target="#transfer-preview" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
active, side, html.EscapeString(kind), urlEsc(value), urlEsc(folder), header.UID, html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
}
if len(messages) == 0 {
b.WriteString(`<div class="ef-empty">Keine Nachrichten.</div>`)
}
}
func renderTransferPreview(kind, value, folder string, index int) string {
raw, label, err := transferRawMessage(kind, value, folder, index)
func renderTransferPreview(kind, value, folder string, index int, uid uint32) string {
raw, label, err := transferRawMessage(kind, value, folder, index, uid)
if err != nil {
return renderEmptyPreviewPane(err.Error())
}
@ -1339,7 +1320,7 @@ func renderTransferPreview(kind, value, folder string, index int) string {
return b.String()
}
func transferRawMessage(kind, value, folder string, index int) ([]byte, string, error) {
func transferRawMessage(kind, value, folder string, index int, uid uint32) ([]byte, string, error) {
switch kind {
case "archive":
path, err := mboxPath(value, folder)
@ -1353,27 +1334,15 @@ func transferRawMessage(kind, value, folder string, index int) ([]byte, string,
if err != nil {
return nil, value, err
}
messages, err := fetchTargetFolderMessages(account, folder)
if err != nil {
return nil, account.DstUser, err
}
if index < 0 || index >= len(messages) {
return nil, account.DstUser, fmt.Errorf("Nachricht waehlen.")
}
return messages[index].Body, account.DstUser, nil
msg, err := fetchTargetMailboxMessage(account, folder, uid)
return msg.Body, account.DstUser, err
default:
account, err := GetAccount(value)
if err != nil {
return nil, value, err
}
messages, err := fetchSourceFolderMessages(account, folder)
if err != nil {
return nil, account.SrcUser, err
}
if index < 0 || index >= len(messages) {
return nil, account.SrcUser, fmt.Errorf("Nachricht waehlen.")
}
return messages[index].Body, account.SrcUser, nil
msg, err := fetchSourceMailboxMessage(account, folder, uid)
return msg.Body, account.SrcUser, err
}
}
@ -1545,33 +1514,35 @@ func sourceFolderRole(folder Folder) string {
return ""
}
func fetchSourceFolderMessages(account Account, folder string) ([]RawMessage, error) {
func fetchSourceFolderHeaders(account Account, folder string) ([]MessageHeader, error) {
if accountProto(account) == "pop3" {
src, err := OpenPOP3Source(account)
if err != nil {
return nil, err
}
defer src.Close()
return collectSourceMessages(src, folder)
return nil, fmt.Errorf("POP3-Headerlisten sind noch nicht unterstuetzt.")
}
src, err := OpenIMAPSource(account)
if err != nil {
return nil, err
}
defer src.Close()
return collectSourceMessages(src, folder)
return src.Headers(folder, mailboxListLimit, 0)
}
func collectSourceMessages(src SourceMailbox, folder string) ([]RawMessage, error) {
var messages []RawMessage
err := src.Fetch(folder, func(m RawMessage) error {
messages = append(messages, m)
return nil
})
if err != nil {
return messages, err
func fetchSourceMailboxMessage(account Account, folder string, uid uint32) (RawMessage, error) {
if accountProto(account) == "pop3" {
return RawMessage{}, fmt.Errorf("POP3-Nachrichtenansicht ist noch nicht unterstuetzt.")
}
return messages, nil
src, err := OpenIMAPSource(account)
if err != nil {
return RawMessage{}, err
}
defer src.Close()
if uid == 0 {
headers, err := src.Headers(folder, 1, 0)
if err != nil {
return RawMessage{}, err
}
uid = firstHeaderUID(headers)
}
return src.FetchOne(folder, uid)
}
func renderInitialSourceMailboxList(account Account) string {
@ -1582,12 +1553,12 @@ func renderInitialSourceMailboxList(account Account) string {
if err != nil {
return fmt.Sprintf(`<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
}
messages, err := fetchSourceFolderMessages(account, folder)
headers, err := fetchSourceFolderHeaders(account, folder)
if err != nil {
return fmt.Sprintf(`<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
}
var b strings.Builder
renderSourceMailboxMessageList(&b, account, folder, messages)
renderSourceMailboxMessageList(&b, account, folder, headers)
return b.String()
}
@ -1599,11 +1570,11 @@ func renderInitialSourceMailboxRead(account Account) string {
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
messages, err := fetchSourceFolderMessages(account, folder)
headers, err := fetchSourceFolderHeaders(account, folder)
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
return renderSourceMailboxRead(account, folder, 0, messages)
return renderSourceMailboxRead(account, folder, firstHeaderUID(headers))
}
func firstSourceFolder(account Account) (string, error) {
@ -1630,29 +1601,18 @@ func firstSourceFolder(account Account) (string, error) {
return folders[0].Name, nil
}
func renderSourceMailboxMessageList(w io.Writer, account Account, folder string, messages []RawMessage) {
func renderSourceMailboxMessageList(w io.Writer, account Account, folder string, messages []MessageHeader) {
fmt.Fprint(w, `<div class="pane-head">Nachrichten</div>`)
for i, raw := range messages {
subject := "(ohne Betreff)"
from := "(ohne Absender)"
date := ""
if msg, err := mail.ReadMessage(bytes.NewReader(raw.Body)); err == nil {
if v := decodeHeader(msg.Header.Get("Subject")); v != "" {
subject = v
}
if v := decodeHeader(msg.Header.Get("From")); v != "" {
from = v
}
date = decodeHeader(msg.Header.Get("Date"))
}
for i, header := range messages {
subject, from, date := headerListFields(header)
active := ""
if i == 0 {
active = " active"
}
fmt.Fprintf(w, `<a class="msg-row%s" href="#" draggable="true" data-source="%s" data-folder="%s" data-index="%d" hx-get="/boxes/source/message?source=%s&amp;folder=%s&amp;index=%d" hx-target="#read" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
fmt.Fprintf(w, `<a class="msg-row%s" href="#" draggable="true" data-source="%s" data-folder="%s" data-uid="%d" hx-get="/boxes/source/message?source=%s&amp;folder=%s&amp;uid=%d" hx-target="#read" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
active,
html.EscapeString(account.Name), html.EscapeString(folder), i,
urlEsc(account.Name), urlEsc(folder), i,
html.EscapeString(account.Name), html.EscapeString(folder), header.UID,
urlEsc(account.Name), urlEsc(folder), header.UID,
html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
}
if len(messages) == 0 {
@ -1660,22 +1620,29 @@ func renderSourceMailboxMessageList(w io.Writer, account Account, folder string,
}
}
func renderSourceMailboxRead(account Account, folder string, index int, messages []RawMessage) string {
if index < 0 || index >= len(messages) {
func renderSourceMailboxRead(account Account, folder string, uid uint32) string {
if uid == 0 {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
raw := messages[index].Body
msg, err := mail.ReadMessage(bytes.NewReader(raw))
raw, err := fetchSourceMailboxMessage(account, folder, uid)
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
return renderSourceMailboxReadMessage(account, folder, raw)
}
func renderSourceMailboxReadMessage(account Account, folder string, raw RawMessage) string {
msg, err := mail.ReadMessage(bytes.NewReader(raw.Body))
var b strings.Builder
if err != nil {
renderMailboxReadPane(&b, "Quelle", account.Name, folder, index, "(unlesbar)", "", "", string(raw))
renderMailboxReadPane(&b, "Quelle", account.Name, folder, 0, "(unlesbar)", "", "", string(raw.Body))
return b.String()
}
renderMailboxReadPane(&b, "Quelle", account.Name, folder, index,
renderMailboxReadPane(&b, "Quelle", account.Name, folder, 0,
decodeHeader(msg.Header.Get("Subject")),
decodeHeader(msg.Header.Get("From")),
decodeHeader(msg.Header.Get("Date")),
messageBody(raw),
messageBody(raw.Body),
)
return b.String()
}
@ -1755,13 +1722,29 @@ func targetFolderRole(folder TargetFolder) string {
return ""
}
func fetchTargetFolderMessages(account Account, folder string) ([]RawMessage, error) {
func fetchTargetFolderHeaders(account Account, folder string) ([]MessageHeader, error) {
dst, err := OpenIMAPTarget(account)
if err != nil {
return nil, err
}
defer dst.Close()
return dst.Fetch(folder)
return dst.Headers(folder, mailboxListLimit, 0)
}
func fetchTargetMailboxMessage(account Account, folder string, uid uint32) (RawMessage, error) {
dst, err := OpenIMAPTarget(account)
if err != nil {
return RawMessage{}, err
}
defer dst.Close()
if uid == 0 {
headers, err := dst.Headers(folder, 1, 0)
if err != nil {
return RawMessage{}, err
}
uid = firstHeaderUID(headers)
}
return dst.FetchOne(folder, uid)
}
func renderInitialTargetMailboxList(account Account) string {
@ -1772,12 +1755,12 @@ func renderInitialTargetMailboxList(account Account) string {
if err != nil {
return fmt.Sprintf(`<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
}
messages, err := fetchTargetFolderMessages(account, folder)
headers, err := fetchTargetFolderHeaders(account, folder)
if err != nil {
return fmt.Sprintf(`<div class="pane-head">Nachrichten</div><div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
}
var b strings.Builder
renderTargetMailboxMessageList(&b, account, folder, messages)
renderTargetMailboxMessageList(&b, account, folder, headers)
return b.String()
}
@ -1789,11 +1772,11 @@ func renderInitialTargetMailboxRead(account Account) string {
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
messages, err := fetchTargetFolderMessages(account, folder)
headers, err := fetchTargetFolderHeaders(account, folder)
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
return renderTargetMailboxRead(account, folder, 0, messages)
return renderTargetMailboxRead(account, folder, firstHeaderUID(headers))
}
func firstTargetFolder(account Account) (string, error) {
@ -1817,29 +1800,18 @@ func firstTargetFolder(account Account) (string, error) {
return folders[0].Name, nil
}
func renderTargetMailboxMessageList(w io.Writer, account Account, folder string, messages []RawMessage) {
func renderTargetMailboxMessageList(w io.Writer, account Account, folder string, messages []MessageHeader) {
fmt.Fprint(w, `<div class="pane-head">Nachrichten</div>`)
for i, raw := range messages {
subject := "(ohne Betreff)"
from := "(ohne Absender)"
date := ""
if msg, err := mail.ReadMessage(bytes.NewReader(raw.Body)); err == nil {
if v := decodeHeader(msg.Header.Get("Subject")); v != "" {
subject = v
}
if v := decodeHeader(msg.Header.Get("From")); v != "" {
from = v
}
date = decodeHeader(msg.Header.Get("Date"))
}
for i, header := range messages {
subject, from, date := headerListFields(header)
active := ""
if i == 0 {
active = " active"
}
fmt.Fprintf(w, `<a class="msg-row%s" href="#" draggable="true" data-target="%s" data-folder="%s" data-index="%d" hx-get="/boxes/target/message?target=%s&amp;folder=%s&amp;index=%d" hx-target="#read" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
fmt.Fprintf(w, `<a class="msg-row%s" href="#" draggable="true" data-target="%s" data-folder="%s" data-uid="%d" hx-get="/boxes/target/message?target=%s&amp;folder=%s&amp;uid=%d" hx-target="#read" hx-swap="innerHTML"><span class="msg-from">%s</span><span class="msg-subject">%s</span><span class="msg-date">%s</span></a>`,
active,
html.EscapeString(account.Name), html.EscapeString(folder), i,
urlEsc(account.Name), urlEsc(folder), i,
html.EscapeString(account.Name), html.EscapeString(folder), header.UID,
urlEsc(account.Name), urlEsc(folder), header.UID,
html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
}
if len(messages) == 0 {
@ -1847,26 +1819,62 @@ func renderTargetMailboxMessageList(w io.Writer, account Account, folder string,
}
}
func renderTargetMailboxRead(account Account, folder string, index int, messages []RawMessage) string {
if index < 0 || index >= len(messages) {
func renderTargetMailboxRead(account Account, folder string, uid uint32) string {
if uid == 0 {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
raw := messages[index].Body
msg, err := mail.ReadMessage(bytes.NewReader(raw))
raw, err := fetchTargetMailboxMessage(account, folder, uid)
if err != nil {
return `<div class="ef-empty">Nachricht waehlen.</div>`
}
return renderTargetMailboxReadMessage(account, folder, raw)
}
func renderTargetMailboxReadMessage(account Account, folder string, raw RawMessage) string {
msg, err := mail.ReadMessage(bytes.NewReader(raw.Body))
var b strings.Builder
if err != nil {
renderMailboxReadPane(&b, "Ziel", account.Name, folder, index, "(unlesbar)", "", "", string(raw))
renderMailboxReadPane(&b, "Ziel", account.Name, folder, 0, "(unlesbar)", "", "", string(raw.Body))
return b.String()
}
renderMailboxReadPane(&b, "Ziel", account.Name, folder, index,
renderMailboxReadPane(&b, "Ziel", account.Name, folder, 0,
decodeHeader(msg.Header.Get("Subject")),
decodeHeader(msg.Header.Get("From")),
decodeHeader(msg.Header.Get("Date")),
messageBody(raw),
messageBody(raw.Body),
)
return b.String()
}
func firstHeaderUID(headers []MessageHeader) uint32 {
if len(headers) == 0 {
return 0
}
return headers[0].UID
}
func parseUIDParam(r *http.Request) uint32 {
uid, _ := strconv.ParseUint(strings.TrimSpace(r.URL.Query().Get("uid")), 10, 32)
return uint32(uid)
}
func headerListFields(header MessageHeader) (subject, from, date string) {
subject = strings.TrimSpace(header.Subject)
if subject == "" {
subject = "(ohne Betreff)"
}
from = strings.TrimSpace(header.From)
if from == "" {
from = "(ohne Absender)"
}
if !header.Date.IsZero() {
date = header.Date.Format(time.RFC1123Z)
} else if !header.InternalDate.IsZero() {
date = header.InternalDate.Format(time.RFC1123Z)
}
return subject, from, date
}
func renderMailboxReadPane(w io.Writer, title, account, folder string, index int, subject, from, date, body string) {
if subject == "" {
subject = "(ohne Betreff)"