Add mirrored source and target mailbox views
This commit is contained in:
parent
d57c9ce1bc
commit
f67bbdce78
2 changed files with 134 additions and 30 deletions
|
|
@ -59,9 +59,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=20260712-13">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-13" defer></script></head>
|
<script src="/static/app.js?v=20260712-14" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<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>
|
<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>
|
||||||
|
|
@ -216,9 +216,9 @@ func emailBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
kind := strings.TrimPrefix(r.URL.Path, "/boxes/")
|
kind := strings.TrimPrefix(r.URL.Path, "/boxes/")
|
||||||
switch kind {
|
switch kind {
|
||||||
case "source":
|
case "source":
|
||||||
renderEmailBoxPage(w, "source", "Quell-Postfach", uniqueAccountValues(accounts, "source"))
|
renderSourceEmailBoxPage(w, accounts)
|
||||||
case "target":
|
case "target":
|
||||||
renderEmailBoxPage(w, "target", "Ziel-Postfach", uniqueAccountValues(accounts, "target"))
|
renderTargetEmailBoxPage(w, accounts)
|
||||||
default:
|
default:
|
||||||
http.Redirect(w, r, "/view", http.StatusSeeOther)
|
http.Redirect(w, r, "/view", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
@ -331,9 +331,9 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
|
||||||
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>Konten-Verwaltung - Mail-Graveyard</title>
|
<title>Konten-Verwaltung - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-13" defer></script></head>
|
<script src="/static/app.js?v=20260712-14" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<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>
|
<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>
|
||||||
|
|
@ -375,9 +375,9 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc
|
||||||
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>Archiv-mbox Verwaltung - Mail-Graveyard</title>
|
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-13" defer></script></head>
|
<script src="/static/app.js?v=20260712-14" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<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>
|
<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>
|
||||||
|
|
@ -442,9 +442,9 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc
|
||||||
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>%s - Mail-Graveyard</title>
|
<title>%s - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-13" defer></script></head>
|
<script src="/static/app.js?v=20260712-14" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<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>
|
<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>
|
||||||
|
|
@ -477,14 +477,14 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc
|
||||||
html.EscapeString(mode.RightTitle), renderTransferPicker(mode.RightKind, accounts, archives))
|
html.EscapeString(mode.RightTitle), renderTransferPicker(mode.RightKind, accounts, archives))
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderEmailBoxPage(w http.ResponseWriter, active, title string, values []string) {
|
func renderSourceEmailBoxPage(w http.ResponseWriter, accounts []Account) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
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>%s - Mail-Graveyard</title>
|
<title>Quell-Postfach - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-13">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-13" defer></script></head>
|
<script src="/static/app.js?v=20260712-14" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<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>
|
<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>
|
||||||
|
|
@ -507,30 +507,124 @@ func renderEmailBoxPage(w http.ResponseWriter, active, title string, values []st
|
||||||
<a class="backstage-item" href="/accounts">Einstellungen</a>
|
<a class="backstage-item" href="/accounts">Einstellungen</a>
|
||||||
</aside>
|
</aside>
|
||||||
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
|
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
|
||||||
<main class="accounts-page email-box-page">
|
<div class="three-pane email-box-view">
|
||||||
<section class="transfer-layout">
|
<aside class="pane pane-tree">%s</aside>
|
||||||
<div class="transfer-panel"><div class="pane-head">%s</div>%s</div>
|
<div class="pane-resizer" data-resize-index="0"></div>
|
||||||
<div class="transfer-panel"><div class="pane-head">Status</div><div class="email-box-detail">Eintrag links waehlen.</div></div>
|
<section class="pane pane-list"><div class="pane-head">Nachrichten</div><div class="ef-empty">Quell-Postfach waehlen.</div></section>
|
||||||
</section>
|
<div class="pane-resizer" data-resize-index="1"></div>
|
||||||
</main>
|
<section class="pane pane-read"><div class="ef-empty">Nachricht waehlen.</div></section>
|
||||||
|
</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>`,
|
||||||
html.EscapeString(title), renderRibbonEmailBoxMenu(active), renderRibbonTransferMenu(""),
|
renderRibbonEmailBoxMenu("source"), renderRibbonTransferMenu(""), renderEmailBoxSourcePane(accounts))
|
||||||
html.EscapeString(title), renderEmailBoxList(values))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderEmailBoxList(values []string) string {
|
func renderTargetEmailBoxPage(w http.ResponseWriter, accounts []Account) {
|
||||||
if len(values) == 0 {
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
return `<div class="ef-empty">Keine Postfaecher eingetragen.</div>`
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
}
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Ziel-Postfach - Mail-Graveyard</title>
|
||||||
|
<link rel="stylesheet" href="/static/style.css?v=20260712-14">
|
||||||
|
<script src="/static/htmx.min.js"></script>
|
||||||
|
<script src="/static/app.js?v=20260712-14" 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 <--> Archiv-MBox</a>
|
||||||
|
<a class="backstage-item transfer-sub" href="/transfer/archiv-postfach">Archiv-MBox <--> Postfach</a>
|
||||||
|
<a class="backstage-item transfer-sub" href="/transfer/postfach-postfach">Postfach <--> 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>
|
||||||
|
<div class="three-pane email-box-view email-box-target-view">
|
||||||
|
<section class="pane pane-list"><div class="pane-head">Nachrichten</div><div class="ef-empty">Ziel-Postfach rechts waehlen.</div></section>
|
||||||
|
<div class="pane-resizer" data-resize-index="0"></div>
|
||||||
|
<section class="pane pane-read"><div class="ef-empty">Nachricht waehlen.</div></section>
|
||||||
|
<div class="pane-resizer" data-resize-index="1"></div>
|
||||||
|
<aside class="pane pane-tree">%s</aside>
|
||||||
|
</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>`,
|
||||||
|
renderRibbonEmailBoxMenu("target"), renderRibbonTransferMenu(""), renderEmailBoxTargetPane(accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderEmailBoxSourcePane(accounts []Account) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(`<div class="email-box-list">`)
|
b.WriteString(`<div class="pane-head archive-head"><span>Quell-Postfach</span><form class="archive-select-form"><select class="archive-select" name="source">`)
|
||||||
for _, value := range values {
|
sourceAccounts := accountsBySource(accounts)
|
||||||
fmt.Fprintf(&b, `<div class="email-box-row">%s</div>`, html.EscapeString(value))
|
if len(sourceAccounts) == 0 {
|
||||||
|
b.WriteString(`<option>Keine Quellen</option>`)
|
||||||
|
}
|
||||||
|
for _, a := range sourceAccounts {
|
||||||
|
label := fmt.Sprintf("%s (%s)", a.SrcUser, strings.ToUpper(accountProto(a)))
|
||||||
|
fmt.Fprintf(&b, `<option value="%s">%s</option>`, html.EscapeString(a.Name), html.EscapeString(label))
|
||||||
|
}
|
||||||
|
b.WriteString(`</select></form></div>`)
|
||||||
|
if len(sourceAccounts) == 0 {
|
||||||
|
b.WriteString(`<div class="ef-empty">Keine Quell-Postfaecher eingetragen.</div>`)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
for _, a := range sourceAccounts {
|
||||||
|
fmt.Fprintf(&b, `<div class="tree-node account">%s</div>`, html.EscapeString(a.SrcUser))
|
||||||
|
fmt.Fprintf(&b, `<div class="tree-node folder"><span class="tree-label">%s</span><span class="tree-count">%s</span></div>`,
|
||||||
|
html.EscapeString(strings.ToUpper(accountProto(a))), html.EscapeString(a.SrcHost))
|
||||||
}
|
}
|
||||||
b.WriteString(`</div>`)
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renderEmailBoxTargetPane(accounts []Account) string {
|
||||||
|
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">`)
|
||||||
|
targets := uniqueAccountValues(accounts, "target")
|
||||||
|
if len(targets) == 0 {
|
||||||
|
b.WriteString(`<option>Keine Ziele</option>`)
|
||||||
|
}
|
||||||
|
for _, target := range targets {
|
||||||
|
fmt.Fprintf(&b, `<option value="%s">%s</option>`, html.EscapeString(target), html.EscapeString(target))
|
||||||
|
}
|
||||||
|
b.WriteString(`</select></form></div>`)
|
||||||
|
if len(targets) == 0 {
|
||||||
|
b.WriteString(`<div class="ef-empty">Keine Ziel-Postfaecher eingetragen.</div>`)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
for _, target := range targets {
|
||||||
|
fmt.Fprintf(&b, `<div class="tree-node account">%s</div>`, html.EscapeString(target))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func accountsBySource(accounts []Account) []Account {
|
||||||
|
out := append([]Account{}, accounts...)
|
||||||
|
for i := 0; i < len(out); i++ {
|
||||||
|
for j := i + 1; j < len(out); j++ {
|
||||||
|
left := strings.ToLower(out[i].SrcUser)
|
||||||
|
right := strings.ToLower(out[j].SrcUser)
|
||||||
|
if right < left {
|
||||||
|
out[i], out[j] = out[j], out[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func accountProto(a Account) string {
|
||||||
|
if strings.TrimSpace(a.SrcProto) != "" {
|
||||||
|
return a.SrcProto
|
||||||
|
}
|
||||||
|
return "imap"
|
||||||
|
}
|
||||||
|
|
||||||
func transferMenuItems(active string) string {
|
func transferMenuItems(active string) string {
|
||||||
items := []transferMode{
|
items := []transferMode{
|
||||||
{Slug: "postfach-archiv", Title: "Postfach <--> Archiv-MBox"},
|
{Slug: "postfach-archiv", Title: "Postfach <--> Archiv-MBox"},
|
||||||
|
|
|
||||||
10
frontend-js/dist/style.css
vendored
10
frontend-js/dist/style.css
vendored
|
|
@ -408,6 +408,16 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
.email-box-page {
|
.email-box-page {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
.email-box-view {
|
||||||
|
grid-template-columns: 220px 6px 340px 6px minmax(300px, 1fr);
|
||||||
|
}
|
||||||
|
.email-box-target-view {
|
||||||
|
grid-template-columns: minmax(300px, 1fr) 6px minmax(340px, 1fr) 6px 260px;
|
||||||
|
}
|
||||||
|
.email-box-target-view .pane-tree {
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 1px solid var(--ol-border);
|
||||||
|
}
|
||||||
.archive-page-layout {
|
.archive-page-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(520px, 1fr) minmax(360px, 460px);
|
grid-template-columns: minmax(520px, 1fr) minmax(360px, 460px);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue