Show archive folder counts

This commit is contained in:
DonVoo 2026-07-12 17:06:58 +02:00
parent 514a7723bf
commit 422308e7e6
3 changed files with 97 additions and 6 deletions

View file

@ -43,9 +43,9 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8"> fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mail-Graveyard</title> <title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260706-6"> <link rel="stylesheet" href="/static/style.css?v=20260712-1">
<script src="/static/htmx.min.js"></script> <script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260706-6" defer></script></head> <script src="/static/app.js?v=20260712-1" defer></script></head>
<body class="ol2013"> <body class="ol2013">
<header class="ribbon"> <header class="ribbon">
<nav class="ribbon-tabs"> <nav class="ribbon-tabs">

View file

@ -9,6 +9,7 @@ import (
"net/mail" "net/mail"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -126,10 +127,9 @@ func renderViewerTree(w http.ResponseWriter) {
found = true found = true
source := sourceDisplayLabel(account) source := sourceDisplayLabel(account)
fmt.Fprintf(w, `<div class="tree-node account">%s</div>`, html.EscapeString(sourceDisplayLabel(account))) fmt.Fprintf(w, `<div class="tree-node account">%s</div>`, html.EscapeString(sourceDisplayLabel(account)))
for _, path := range mboxes { for _, folder := range archiveFolderInfos(mboxes) {
folder := strings.TrimSuffix(filepath.Base(path), ".mbox") fmt.Fprintf(w, `<a class="tree-node folder" href="#" hx-get="/view?source=%s&amp;folder=%s" hx-target="#list" hx-swap="innerHTML"><span class="tree-label">%s</span><span class="tree-count">%d</span></a>`,
fmt.Fprintf(w, `<a class="tree-node" href="#" hx-get="/view?source=%s&amp;folder=%s" hx-target="#list" hx-swap="innerHTML">%s</a>`, urlEsc(source), urlEsc(folder.Name), html.EscapeString(folder.Label), folder.Count)
urlEsc(source), urlEsc(folder), html.EscapeString(folder))
} }
} }
if !found { if !found {
@ -181,6 +181,82 @@ func renderMessageList(w http.ResponseWriter, account, folder, q string) {
} }
} }
type archiveFolderInfo struct {
Name string
Label string
Count int
}
func archiveFolderInfos(paths []string) []archiveFolderInfo {
out := make([]archiveFolderInfo, 0, len(paths))
for _, path := range paths {
name := strings.TrimSuffix(filepath.Base(path), ".mbox")
entries, err := ReadMboxList(path)
count := 0
if err == nil {
count = len(entries)
}
out = append(out, archiveFolderInfo{
Name: name,
Label: archiveFolderLabel(name),
Count: count,
})
}
sort.SliceStable(out, func(i, j int) bool {
return archiveFolderSortKey(out[i].Name) < archiveFolderSortKey(out[j].Name)
})
return out
}
func archiveFolderLabel(name string) string {
name = strings.TrimSpace(name)
switch strings.ToLower(name) {
case "", "inbox":
return "Posteingang"
case "drafts", "entw&apw-rfe", "entwürfe", "entwuerfe":
return "Entwürfe"
case "sent", "gesendet", "gesendete", "gesendete objekte", "gesendete elemente":
return "Gesendet"
case "trash", "papierkorb", "gelöscht", "geloescht", "gelöschte objekte", "geloeschte objekte":
return "Papierkorb"
case "archive", "archiv":
return "Archiv"
case "spam", "junk":
return "Spam"
}
parts := strings.FieldsFunc(name, func(r rune) bool {
return r == '/' || r == '\\'
})
if len(parts) == 0 {
parts = strings.Split(name, "_")
}
label := strings.TrimSpace(parts[len(parts)-1])
if label == "" {
return name
}
return label
}
func archiveFolderSortKey(name string) string {
lc := strings.ToLower(strings.TrimSpace(name))
switch lc {
case "inbox":
return "00:" + lc
case "drafts", "entw&apw-rfe", "entwürfe", "entwuerfe":
return "01:" + lc
case "sent", "gesendet", "gesendete", "gesendete objekte", "gesendete elemente":
return "02:" + lc
case "spam", "junk":
return "03:" + lc
case "trash", "papierkorb", "gelöscht", "geloescht", "gelöschte objekte", "geloeschte objekte":
return "04:" + lc
case "archive", "archiv":
return "05:" + lc
default:
return "10:" + lc
}
}
func manualCopyHandler(w http.ResponseWriter, r *http.Request) { func manualCopyHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) http.Error(w, "method not allowed", http.StatusMethodNotAllowed)

View file

@ -123,6 +123,21 @@ body.resizing { cursor: col-resize; user-select: none; }
/* Ordnerbaum */ /* Ordnerbaum */
.tree-node { padding: 4px 12px 4px 24px; cursor: pointer; white-space: nowrap; } .tree-node { padding: 4px 12px 4px 24px; cursor: pointer; white-space: nowrap; }
a.tree-node { display: block; color: inherit; text-decoration: none; } a.tree-node { display: block; color: inherit; text-decoration: none; }
.tree-node.folder {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
}
.tree-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.tree-count {
color: var(--ol-muted);
font-size: 11px;
}
.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); }