Polish source mailbox reading panes
This commit is contained in:
parent
561450662c
commit
548e0fa741
2 changed files with 69 additions and 21 deletions
|
|
@ -311,6 +311,7 @@ func emailBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
fmt.Fprint(w, renderEmailBoxSourcePane(accounts, selected.Name))
|
fmt.Fprint(w, renderEmailBoxSourcePane(accounts, selected.Name))
|
||||||
fmt.Fprintf(w, `<div id="list" hx-swap-oob="innerHTML">%s</div>`, renderInitialSourceMailboxList(selected))
|
fmt.Fprintf(w, `<div id="list" hx-swap-oob="innerHTML">%s</div>`, renderInitialSourceMailboxList(selected))
|
||||||
|
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderInitialSourceMailboxRead(selected))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderSourceEmailBoxPage(w, r, accounts, r.URL.Query().Get("source"))
|
renderSourceEmailBoxPage(w, r, accounts, r.URL.Query().Get("source"))
|
||||||
|
|
@ -340,6 +341,7 @@ func sourceMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderSourceMailboxMessageList(w, account, folder, messages)
|
renderSourceMailboxMessageList(w, account, folder, messages)
|
||||||
|
fmt.Fprintf(w, `<div id="read" hx-swap-oob="innerHTML">%s</div>`, renderSourceMailboxRead(account, folder, 0, messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
|
func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -360,22 +362,8 @@ func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if index < 0 || index >= len(messages) {
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
http.Error(w, "message index out of range", http.StatusNotFound)
|
fmt.Fprint(w, renderSourceMailboxRead(account, folder, index, messages))
|
||||||
return
|
|
||||||
}
|
|
||||||
raw := messages[index].Body
|
|
||||||
msg, err := mail.ReadMessage(bytes.NewReader(raw))
|
|
||||||
if err != nil {
|
|
||||||
renderReadPaneWithSource(w, account.Name, folder, "", index, "(unlesbar)", "", "", string(raw))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
renderReadPaneWithSource(w, account.Name, folder, "", index,
|
|
||||||
decodeHeader(msg.Header.Get("Subject")),
|
|
||||||
decodeHeader(msg.Header.Get("From")),
|
|
||||||
decodeHeader(msg.Header.Get("Date")),
|
|
||||||
messageBody(raw),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
|
func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -885,10 +873,10 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
||||||
<div class="pane-resizer" data-resize-index="0"></div>
|
<div class="pane-resizer" data-resize-index="0"></div>
|
||||||
<section class="pane pane-list" id="list">%s</section>
|
<section class="pane pane-list" id="list">%s</section>
|
||||||
<div class="pane-resizer" data-resize-index="1"></div>
|
<div class="pane-resizer" data-resize-index="1"></div>
|
||||||
<section class="pane pane-read"><div class="ef-empty">Nachricht waehlen.</div></section>
|
<section class="pane pane-read" id="read">%s</section>
|
||||||
</div>
|
</div>
|
||||||
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
|
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
|
||||||
renderRibbonEmailBoxMenu("source"), renderRibbonTransferMenu(""), renderRibbonActions(false, user), renderEmailBoxSourcePane(accounts, selectedAccount.Name), renderInitialSourceMailboxList(selectedAccount))
|
renderRibbonEmailBoxMenu("source"), renderRibbonTransferMenu(""), renderRibbonActions(false, user), renderEmailBoxSourcePane(accounts, selectedAccount.Name), renderInitialSourceMailboxList(selectedAccount), renderInitialSourceMailboxRead(selectedAccount))
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts []Account) {
|
func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts []Account) {
|
||||||
|
|
@ -992,11 +980,27 @@ func renderSourceFolders(b *strings.Builder, account Account) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
fmt.Fprintf(b, `<a class="tree-node folder" href="#" hx-get="/boxes/source/messages?source=%s&folder=%s" hx-target="#list" hx-swap="innerHTML"><span class="tree-label">%s</span><span class="tree-count">%s</span></a>`,
|
fmt.Fprintf(b, `<a class="tree-node folder" href="#" title="%s" hx-get="/boxes/source/messages?source=%s&folder=%s" hx-target="#list" hx-swap="innerHTML"><span class="tree-label">%s</span><span class="tree-count">%s</span></a>`,
|
||||||
urlEsc(account.Name), urlEsc(folder.Name), html.EscapeString(folder.Name), html.EscapeString(strings.Join(folder.Attrs, " ")))
|
html.EscapeString(strings.Join(folder.Attrs, " ")), urlEsc(account.Name), urlEsc(folder.Name), html.EscapeString(folder.Name), html.EscapeString(sourceFolderRole(folder)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sourceFolderRole(folder Folder) string {
|
||||||
|
for _, attr := range folder.Attrs {
|
||||||
|
switch strings.ToLower(attr) {
|
||||||
|
case `\sent`:
|
||||||
|
return "Gesendet"
|
||||||
|
case `\drafts`:
|
||||||
|
return "Entwurf"
|
||||||
|
case `\trash`:
|
||||||
|
return "Papierkorb"
|
||||||
|
case `\junk`:
|
||||||
|
return "Spam"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func fetchSourceFolderMessages(account Account, folder string) ([]RawMessage, error) {
|
func fetchSourceFolderMessages(account Account, folder string) ([]RawMessage, error) {
|
||||||
if accountProto(account) == "pop3" {
|
if accountProto(account) == "pop3" {
|
||||||
src, err := OpenPOP3Source(account)
|
src, err := OpenPOP3Source(account)
|
||||||
|
|
@ -1031,6 +1035,21 @@ func renderInitialSourceMailboxList(account Account) string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renderInitialSourceMailboxRead(account Account) string {
|
||||||
|
if account.Name == "" {
|
||||||
|
return `<div class="ef-empty">Nachricht waehlen.</div>`
|
||||||
|
}
|
||||||
|
folder, err := firstSourceFolder(account)
|
||||||
|
if err != nil {
|
||||||
|
return `<div class="ef-empty">Nachricht waehlen.</div>`
|
||||||
|
}
|
||||||
|
messages, err := fetchSourceFolderMessages(account, folder)
|
||||||
|
if err != nil {
|
||||||
|
return `<div class="ef-empty">Nachricht waehlen.</div>`
|
||||||
|
}
|
||||||
|
return renderSourceMailboxRead(account, folder, 0, messages)
|
||||||
|
}
|
||||||
|
|
||||||
func firstSourceFolder(account Account) (string, error) {
|
func firstSourceFolder(account Account) (string, error) {
|
||||||
if accountProto(account) == "pop3" {
|
if accountProto(account) == "pop3" {
|
||||||
return "INBOX", nil
|
return "INBOX", nil
|
||||||
|
|
@ -1070,7 +1089,12 @@ func renderSourceMailboxMessageList(w io.Writer, account Account, folder string,
|
||||||
}
|
}
|
||||||
date = decodeHeader(msg.Header.Get("Date"))
|
date = decodeHeader(msg.Header.Get("Date"))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, `<a class="msg-row" href="#" draggable="true" data-source="%s" data-folder="%s" data-index="%d" hx-get="/boxes/source/message?source=%s&folder=%s&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>`,
|
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&folder=%s&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>`,
|
||||||
|
active,
|
||||||
html.EscapeString(account.Name), html.EscapeString(folder), i,
|
html.EscapeString(account.Name), html.EscapeString(folder), i,
|
||||||
urlEsc(account.Name), urlEsc(folder), i,
|
urlEsc(account.Name), urlEsc(folder), i,
|
||||||
html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
|
html.EscapeString(from), html.EscapeString(subject), html.EscapeString(date))
|
||||||
|
|
@ -1080,6 +1104,26 @@ 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) {
|
||||||
|
return `<div class="ef-empty">Nachricht waehlen.</div>`
|
||||||
|
}
|
||||||
|
raw := messages[index].Body
|
||||||
|
msg, err := mail.ReadMessage(bytes.NewReader(raw))
|
||||||
|
var b strings.Builder
|
||||||
|
if err != nil {
|
||||||
|
renderReadPane(&b, "Quelle", account.Name, folder, "", index, "(unlesbar)", "", "", string(raw))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
renderReadPane(&b, "Quelle", account.Name, folder, "", index,
|
||||||
|
decodeHeader(msg.Header.Get("Subject")),
|
||||||
|
decodeHeader(msg.Header.Get("From")),
|
||||||
|
decodeHeader(msg.Header.Get("Date")),
|
||||||
|
messageBody(raw),
|
||||||
|
)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
func renderEmailBoxTargetPane(accounts []Account) string {
|
func renderEmailBoxTargetPane(accounts []Account) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(`<div class="pane-head archive-head"><span>Ziel-Postfach</span><form class="archive-select-form"><select class="archive-select" name="target">`)
|
b.WriteString(`<div class="pane-head archive-head"><span>Ziel-Postfach</span><form class="archive-select-form"><select class="archive-select" name="target">`)
|
||||||
|
|
|
||||||
4
frontend-js/dist/style.css
vendored
4
frontend-js/dist/style.css
vendored
|
|
@ -346,6 +346,7 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
color: var(--ol-muted);
|
color: var(--ol-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
.tree-count:empty { display: none; }
|
||||||
.tree-node.account { padding-left: 12px; font-weight: 600; color: var(--ol-blue-dark); }
|
.tree-node.account { padding-left: 12px; font-weight: 600; color: var(--ol-blue-dark); }
|
||||||
.tree-node:hover { background: #e5eff8; }
|
.tree-node:hover { background: #e5eff8; }
|
||||||
.tree-node.active { background: var(--ol-sel); box-shadow: inset 2px 0 0 var(--ol-blue); }
|
.tree-node.active { background: var(--ol-sel); box-shadow: inset 2px 0 0 var(--ol-blue); }
|
||||||
|
|
@ -356,7 +357,10 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
border-bottom: 1px solid #ececec;
|
border-bottom: 1px solid #ececec;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex; flex-direction: column; gap: 2px;
|
display: flex; flex-direction: column; gap: 2px;
|
||||||
|
color: var(--ol-text);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.msg-row:visited { color: var(--ol-text); }
|
||||||
.msg-row:hover { background: #eef6fd; }
|
.msg-row:hover { background: #eef6fd; }
|
||||||
.msg-row.dragging { opacity: .55; }
|
.msg-row.dragging { opacity: .55; }
|
||||||
.msg-row.active { background: var(--ol-sel); border-left: 3px solid var(--ol-blue); padding-left: 9px; }
|
.msg-row.active { background: var(--ol-sel); border-left: 3px solid var(--ol-blue); padding-left: 9px; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue