Add email box top dropdown

This commit is contained in:
DonVoo 2026-07-12 23:59:25 +02:00
parent e8fc294c5d
commit d57c9ce1bc
2 changed files with 136 additions and 17 deletions

View file

@ -27,6 +27,8 @@ func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/archives/delete", archiveDeleteHandler)
mux.HandleFunc("/transfer", transferHandler)
mux.HandleFunc("/transfer/", transferHandler)
mux.HandleFunc("/boxes/source", emailBoxHandler)
mux.HandleFunc("/boxes/target", emailBoxHandler)
mux.HandleFunc("/migrate/run", migrateRunHandler) // TODO Codex: RunMigration im Hintergrund
mux.HandleFunc("/migrate/status", migrateStatusHandler) // TODO Codex: jobs-Fortschritt (HTMX-Poll)
@ -50,21 +52,21 @@ func homeHandler(w http.ResponseWriter, r *http.Request) {
// renderShell ist das Grundgeruest im Outlook-2013-Look:
//
// [ Ribbon: Archiv-Boxen | Mail-Transfer | Einstellungen | Mail-Graveyard ]
// [ Ribbon: Email-Box | Mail-Transfer | Einstellungen | Mail-Graveyard ]
// [ Archiv-mbox | Nachrichtenliste | Quelle/Target ]
func renderShell(w http.ResponseWriter, active, readingPane string) {
w.Header().Set("Content-Type", "text/html; 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">
<title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-12">
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-12" defer></script></head>
<script src="/static/app.js?v=20260712-13" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
<a class="tab %s" href="/view">Archiv-Boxen</a>
%s
%s
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
@ -98,7 +100,7 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
</div>
<footer class="statusbar"><span id="mig-status"
hx-get="/migrate/status" hx-trigger="every 3s" hx-swap="innerHTML">bereit</span></footer>
</body></html>`, active, renderRibbonTransferMenu(""), renderInitialReadPane(readingPane))
</body></html>`, renderRibbonEmailBoxMenu("archive"), renderRibbonTransferMenu(""), renderInitialReadPane(readingPane))
}
func accountsHandler(w http.ResponseWriter, r *http.Request) {
@ -205,6 +207,23 @@ func transferHandler(w http.ResponseWriter, r *http.Request) {
renderTransferPage(w, transferModeBySlug(mode), accounts, archiveMailboxes(accounts))
}
func emailBoxHandler(w http.ResponseWriter, r *http.Request) {
accounts, err := ListAccounts()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
kind := strings.TrimPrefix(r.URL.Path, "/boxes/")
switch kind {
case "source":
renderEmailBoxPage(w, "source", "Quell-Postfach", uniqueAccountValues(accounts, "source"))
case "target":
renderEmailBoxPage(w, "target", "Ziel-Postfach", uniqueAccountValues(accounts, "target"))
default:
http.Redirect(w, r, "/view", http.StatusSeeOther)
}
}
func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@ -312,14 +331,14 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Konten-Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-12">
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-12" defer></script></head>
<script src="/static/app.js?v=20260712-13" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
<a class="tab" href="/view">Archiv-Boxen</a>
%s
%s
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
@ -337,7 +356,7 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
<main class="accounts-page">`, renderRibbonTransferMenu(""))
<main class="accounts-page">`, renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu(""))
if msg != "" {
fmt.Fprintf(w, `<div class="notice ok">%s</div>`, html.EscapeString(msg))
}
@ -356,14 +375,14 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-12">
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-12" defer></script></head>
<script src="/static/app.js?v=20260712-13" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
<a class="tab" href="/view">Archiv-Boxen</a>
%s
%s
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
@ -381,7 +400,7 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
<main class="accounts-page archive-page">`, renderRibbonTransferMenu(""))
<main class="accounts-page archive-page">`, renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu(""))
if msg != "" {
fmt.Fprintf(w, `<div class="notice ok">%s</div>`, html.EscapeString(msg))
}
@ -423,14 +442,14 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>%s - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-12">
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-12" defer></script></head>
<script src="/static/app.js?v=20260712-13" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
<a class="tab" href="/view">Archiv-Boxen</a>
%s
%s
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
@ -453,11 +472,65 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc
</section>
</main>
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
html.EscapeString(mode.Title), renderRibbonTransferMenu(mode.Slug), transferMenuItems(mode.Slug),
html.EscapeString(mode.Title), renderRibbonEmailBoxMenu(""), renderRibbonTransferMenu(mode.Slug), transferMenuItems(mode.Slug),
html.EscapeString(mode.LeftTitle), renderTransferPicker(mode.LeftKind, accounts, archives),
html.EscapeString(mode.RightTitle), renderTransferPicker(mode.RightKind, accounts, archives))
}
func renderEmailBoxPage(w http.ResponseWriter, active, title string, values []string) {
w.Header().Set("Content-Type", "text/html; 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">
<title>%s - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-13" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
%s
%s
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
<div class="ribbon-title">Mail-Graveyard</div>
</header>
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
<a class="backstage-item" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/view">Import/Export</a>
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
<a class="backstage-item transfer-sub" href="/transfer/postfach-archiv">Postfach &lt;--&gt; Archiv-MBox</a>
<a class="backstage-item transfer-sub" href="/transfer/archiv-postfach">Archiv-MBox &lt;--&gt; Postfach</a>
<a class="backstage-item transfer-sub" href="/transfer/postfach-postfach">Postfach &lt;--&gt; Postfach</a>
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
<main class="accounts-page email-box-page">
<section class="transfer-layout">
<div class="transfer-panel"><div class="pane-head">%s</div>%s</div>
<div class="transfer-panel"><div class="pane-head">Status</div><div class="email-box-detail">Eintrag links waehlen.</div></div>
</section>
</main>
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
html.EscapeString(title), renderRibbonEmailBoxMenu(active), renderRibbonTransferMenu(""),
html.EscapeString(title), renderEmailBoxList(values))
}
func renderEmailBoxList(values []string) string {
if len(values) == 0 {
return `<div class="ef-empty">Keine Postfaecher eingetragen.</div>`
}
var b strings.Builder
b.WriteString(`<div class="email-box-list">`)
for _, value := range values {
fmt.Fprintf(&b, `<div class="email-box-row">%s</div>`, html.EscapeString(value))
}
b.WriteString(`</div>`)
return b.String()
}
func transferMenuItems(active string) string {
items := []transferMode{
{Slug: "postfach-archiv", Title: "Postfach <--> Archiv-MBox"},
@ -475,6 +548,34 @@ func transferMenuItems(active string) string {
return b.String()
}
func renderRibbonEmailBoxMenu(active string) string {
items := []transferMode{
{Slug: "archive", Title: "Archiv-Postfach"},
{Slug: "source", Title: "Quell-Postfach"},
{Slug: "target", Title: "Ziel-Postfach"},
}
paths := map[string]string{
"archive": "/view",
"source": "/boxes/source",
"target": "/boxes/target",
}
buttonClass := "tab ribbon-menu-button"
if active != "" {
buttonClass += " active"
}
var b strings.Builder
fmt.Fprintf(&b, `<div class="ribbon-menu"><button class="%s" type="button" aria-expanded="false" data-ribbon-menu-toggle>Email-Box</button><div class="ribbon-dropdown">`, buttonClass)
for _, item := range items {
class := "ribbon-dropdown-item"
if item.Slug == active {
class += " active"
}
fmt.Fprintf(&b, `<a class="%s" href="%s">%s</a>`, class, paths[item.Slug], html.EscapeString(item.Title))
}
b.WriteString(`</div></div>`)
return b.String()
}
func renderRibbonTransferMenu(active string) string {
items := []transferMode{
{Slug: "postfach-archiv", Title: "Postfach <--> Archiv-MBox"},