Add archive mailbox selector
This commit is contained in:
parent
422308e7e6
commit
cff776dc8c
3 changed files with 94 additions and 34 deletions
|
|
@ -36,6 +36,7 @@ func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderShell ist das Grundgeruest im Outlook-2013-Look:
|
// renderShell ist das Grundgeruest im Outlook-2013-Look:
|
||||||
|
//
|
||||||
// [ Ribbon: Archiv-Boxen | Import/Export | Postfach-Verwaltung | Mail-Transfer | Einstellungen | Mail-Graveyard ]
|
// [ Ribbon: Archiv-Boxen | Import/Export | Postfach-Verwaltung | Mail-Transfer | Einstellungen | Mail-Graveyard ]
|
||||||
// [ Archiv-mbox | Nachrichtenliste | Quelle/Target ]
|
// [ Archiv-mbox | Nachrichtenliste | Quelle/Target ]
|
||||||
func renderShell(w http.ResponseWriter, active, readingPane string) {
|
func renderShell(w http.ResponseWriter, active, readingPane string) {
|
||||||
|
|
@ -43,13 +44,13 @@ 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-1">
|
<link rel="stylesheet" href="/static/style.css?v=20260712-2">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-1" defer></script></head>
|
<script src="/static/app.js?v=20260712-2" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<nav class="ribbon-tabs">
|
<nav class="ribbon-tabs">
|
||||||
<a class="tab %s" href="/view">Archiv-Boxen</a>
|
<a class="tab %s" href="/view"><span class="mail-logo" aria-hidden="true"></span><span>Archiv-Boxen</span></a>
|
||||||
<a class="tab" href="/view">Import/Export</a>
|
<a class="tab" href="/view">Import/Export</a>
|
||||||
<a class="tab" href="/accounts">Postfach-Verwaltung</a>
|
<a class="tab" href="/accounts">Postfach-Verwaltung</a>
|
||||||
<a class="tab" href="/view">Mail-Transfer</a>
|
<a class="tab" href="/view">Mail-Transfer</a>
|
||||||
|
|
@ -60,7 +61,7 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
|
||||||
<div class="three-pane">
|
<div class="three-pane">
|
||||||
<aside class="pane pane-tree" id="tree"
|
<aside class="pane pane-tree" id="tree"
|
||||||
hx-get="/view" hx-trigger="load" hx-target="#tree" hx-swap="innerHTML">
|
hx-get="/view" hx-trigger="load" hx-target="#tree" hx-swap="innerHTML">
|
||||||
<div class="pane-head">Postfächer</div>
|
<div class="pane-head">Archiv-Mailbox</div>
|
||||||
</aside>
|
</aside>
|
||||||
<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">
|
<section class="pane pane-list" id="list">
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Header.Get("HX-Request") == "true" {
|
if r.Header.Get("HX-Request") == "true" {
|
||||||
renderViewerTree(w)
|
renderViewerTree(w, account)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderShell(w, "", `<div class="ef-empty">Backup-Postfach links waehlen.</div>`)
|
renderShell(w, "", `<div class="ef-empty">Backup-Postfach links waehlen.</div>`)
|
||||||
|
|
@ -106,34 +106,47 @@ func forwardHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// selbst nutzt IMAP APPEND.
|
// selbst nutzt IMAP APPEND.
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderViewerTree(w http.ResponseWriter) {
|
func renderViewerTree(w http.ResponseWriter, selected string) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
fmt.Fprint(w, `<div class="pane-head">Originale</div>`)
|
accounts := viewerAccounts()
|
||||||
entries, err := os.ReadDir(Cfg.MboxRoot)
|
if len(accounts) == 0 {
|
||||||
if err != nil {
|
fmt.Fprint(w, `<div class="pane-head archive-head"><span>Archiv-Mailbox</span></div><div class="ef-empty">Noch keine mbox-Backups.</div>`)
|
||||||
fmt.Fprintf(w, `<div class="ef-empty">%s</div>`, html.EscapeString(err.Error()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
selected = resolveViewerAccount(selected, selected)
|
||||||
|
if selected == "" {
|
||||||
|
selected = accounts[0].Name
|
||||||
|
} else {
|
||||||
found := false
|
found := false
|
||||||
for _, entry := range entries {
|
for _, account := range accounts {
|
||||||
if !entry.IsDir() {
|
if account.Name == selected {
|
||||||
continue
|
|
||||||
}
|
|
||||||
account := entry.Name()
|
|
||||||
mboxes, _ := filepath.Glob(filepath.Join(Cfg.MboxRoot, account, "*.mbox"))
|
|
||||||
if len(mboxes) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found = true
|
found = true
|
||||||
source := sourceDisplayLabel(account)
|
break
|
||||||
fmt.Fprintf(w, `<div class="tree-node account">%s</div>`, html.EscapeString(sourceDisplayLabel(account)))
|
|
||||||
for _, folder := range archiveFolderInfos(mboxes) {
|
|
||||||
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>`,
|
|
||||||
urlEsc(source), urlEsc(folder.Name), html.EscapeString(folder.Label), folder.Count)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
fmt.Fprint(w, `<div class="ef-empty">Noch keine mbox-Backups.</div>`)
|
selected = accounts[0].Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, `<div class="pane-head archive-head"><span>Archiv-Mailbox</span><form class="archive-select-form" hx-get="/view" hx-target="#tree" hx-swap="innerHTML" hx-trigger="change from:#archive-mailbox-select"><select id="archive-mailbox-select" class="archive-select" name="source">`)
|
||||||
|
for _, account := range accounts {
|
||||||
|
attr := ""
|
||||||
|
if account.Name == selected {
|
||||||
|
attr = ` selected`
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, `<option value="%s"%s>%s</option>`, html.EscapeString(account.DisplayLabel), attr, html.EscapeString(account.SourceLabel))
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, `</select></form></div>`)
|
||||||
|
|
||||||
|
mboxes, _ := filepath.Glob(filepath.Join(Cfg.MboxRoot, selected, "*.mbox"))
|
||||||
|
if len(mboxes) == 0 {
|
||||||
|
fmt.Fprint(w, `<div class="ef-empty">Keine Ordner in dieser Archiv-Mailbox.</div>`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
source := sourceDisplayLabel(selected)
|
||||||
|
for _, folder := range archiveFolderInfos(mboxes) {
|
||||||
|
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>`,
|
||||||
|
urlEsc(source), urlEsc(folder.Name), html.EscapeString(folder.Label), folder.Count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
46
frontend-js/dist/style.css
vendored
46
frontend-js/dist/style.css
vendored
|
|
@ -41,12 +41,41 @@ body.ol2013 {
|
||||||
.ribbon-tabs { display: flex; gap: 2px; height: 100%; }
|
.ribbon-tabs { display: flex; gap: 2px; height: 100%; }
|
||||||
.tab {
|
.tab {
|
||||||
display: flex; align-items: center;
|
display: flex; align-items: center;
|
||||||
|
gap: 8px;
|
||||||
padding: 0 16px; height: 100%;
|
padding: 0 16px; height: 100%;
|
||||||
color: #eaf3fb; text-decoration: none;
|
color: #eaf3fb; text-decoration: none;
|
||||||
border-bottom: 3px solid transparent;
|
border-bottom: 3px solid transparent;
|
||||||
}
|
}
|
||||||
.tab:hover { background: var(--ol-blue-dark); color: #fff; }
|
.tab:hover { background: var(--ol-blue-dark); color: #fff; }
|
||||||
.tab.active { background: #fff; color: var(--ol-blue); border-bottom-color: #fff; }
|
.tab.active { background: #fff; color: var(--ol-blue); border-bottom-color: #fff; }
|
||||||
|
.mail-logo {
|
||||||
|
position: relative;
|
||||||
|
width: 17px;
|
||||||
|
height: 12px;
|
||||||
|
border: 2px solid currentColor;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.mail-logo::before,
|
||||||
|
.mail-logo::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
width: 10px;
|
||||||
|
height: 2px;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
.mail-logo::before {
|
||||||
|
left: -1px;
|
||||||
|
transform: rotate(32deg);
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
.mail-logo::after {
|
||||||
|
right: -1px;
|
||||||
|
transform: rotate(-32deg);
|
||||||
|
transform-origin: right center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Viewer */
|
/* Viewer */
|
||||||
.three-pane,
|
.three-pane,
|
||||||
|
|
@ -107,6 +136,23 @@ body.resizing { cursor: col-resize; user-select: none; }
|
||||||
color: var(--ol-text);
|
color: var(--ol-text);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
.archive-head {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.archive-select-form {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.archive-select {
|
||||||
|
width: 100%;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
color: var(--ol-text);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.searchbar {
|
.searchbar {
|
||||||
position: sticky; top: 30px; z-index: 2;
|
position: sticky; top: 30px; z-index: 2;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue