Show archive folder counts
This commit is contained in:
parent
514a7723bf
commit
422308e7e6
3 changed files with 97 additions and 6 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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&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&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)
|
||||||
|
|
|
||||||
15
frontend-js/dist/style.css
vendored
15
frontend-js/dist/style.css
vendored
|
|
@ -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); }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue