Generalize sidebar categorization logic
This commit is contained in:
parent
9e2c420461
commit
6ba418c9b2
5 changed files with 149 additions and 143 deletions
|
@ -63,12 +63,16 @@ func registerRoutes(p *alps.GoPlugin) {
|
||||||
p.POST("/settings", handleSettings)
|
p.POST("/settings", handleSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MailboxRenderData struct {
|
type IMAPBaseRenderData struct {
|
||||||
alps.BaseRenderData
|
alps.BaseRenderData
|
||||||
Mailbox *MailboxStatus
|
|
||||||
Inbox *MailboxStatus
|
|
||||||
CategorizedMailboxes CategorizedMailboxes
|
CategorizedMailboxes CategorizedMailboxes
|
||||||
Mailboxes []MailboxInfo
|
Mailboxes []MailboxInfo
|
||||||
|
Mailbox *MailboxStatus
|
||||||
|
Inbox *MailboxStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailboxRenderData struct {
|
||||||
|
IMAPBaseRenderData
|
||||||
Messages []IMAPMessage
|
Messages []IMAPMessage
|
||||||
PrevPage, NextPage int
|
PrevPage, NextPage int
|
||||||
Query string
|
Query string
|
||||||
|
@ -87,21 +91,52 @@ type CategorizedMailboxes struct {
|
||||||
Additional []*MailboxInfo
|
Additional []*MailboxInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func categorizeMailboxes(mailboxes []MailboxInfo,
|
func newIMAPBaseRenderData(ctx *alps.Context,
|
||||||
inbox *MailboxStatus, active *MailboxStatus) CategorizedMailboxes {
|
base *alps.BaseRenderData) (*IMAPBaseRenderData, error) {
|
||||||
|
|
||||||
var out CategorizedMailboxes
|
mboxName, err := url.PathUnescape(ctx.Param("mbox"))
|
||||||
mmap := map[string]**MailboxInfo{
|
if err != nil {
|
||||||
"INBOX": &out.Common.Inbox,
|
return nil, echo.NewHTTPError(http.StatusBadRequest, err)
|
||||||
"Drafts": &out.Common.Drafts,
|
|
||||||
"Sent": &out.Common.Sent,
|
|
||||||
"Junk": &out.Common.Junk,
|
|
||||||
"Trash": &out.Common.Trash,
|
|
||||||
"Archive": &out.Common.Archive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mailboxes []MailboxInfo
|
||||||
|
var active, inbox *MailboxStatus
|
||||||
|
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
||||||
|
var err error
|
||||||
|
if mailboxes, err = listMailboxes(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mboxName != "" {
|
||||||
|
if active, err = getMailboxStatus(c, mboxName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mboxName == "INBOX" {
|
||||||
|
inbox = active
|
||||||
|
} else {
|
||||||
|
if inbox, err = getMailboxStatus(c, "INBOX"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var categorized CategorizedMailboxes
|
||||||
|
mmap := map[string]**MailboxInfo{
|
||||||
|
"INBOX": &categorized.Common.Inbox,
|
||||||
|
"Drafts": &categorized.Common.Drafts,
|
||||||
|
"Sent": &categorized.Common.Sent,
|
||||||
|
"Junk": &categorized.Common.Junk,
|
||||||
|
"Trash": &categorized.Common.Trash,
|
||||||
|
"Archive": &categorized.Common.Archive,
|
||||||
|
}
|
||||||
|
|
||||||
for i, _ := range mailboxes {
|
for i, _ := range mailboxes {
|
||||||
// Populate unseen & active states
|
// Populate unseen & active states
|
||||||
if mailboxes[i].Name == active.Name {
|
if active != nil && mailboxes[i].Name == active.Name {
|
||||||
mailboxes[i].Unseen = int(active.Unseen)
|
mailboxes[i].Unseen = int(active.Unseen)
|
||||||
mailboxes[i].Active = true
|
mailboxes[i].Active = true
|
||||||
}
|
}
|
||||||
|
@ -112,18 +147,36 @@ func categorizeMailboxes(mailboxes []MailboxInfo,
|
||||||
if ptr, ok := mmap[mailboxes[i].Name]; ok {
|
if ptr, ok := mmap[mailboxes[i].Name]; ok {
|
||||||
*ptr = &mailboxes[i]
|
*ptr = &mailboxes[i]
|
||||||
} else {
|
} else {
|
||||||
out.Additional = append(out.Additional, &mailboxes[i])
|
categorized.Additional = append(
|
||||||
|
categorized.Additional, &mailboxes[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
|
||||||
|
return &IMAPBaseRenderData{
|
||||||
|
BaseRenderData: *base,
|
||||||
|
CategorizedMailboxes: categorized,
|
||||||
|
Mailboxes: mailboxes,
|
||||||
|
Inbox: inbox,
|
||||||
|
Mailbox: active,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetMailbox(ctx *alps.Context) error {
|
func handleGetMailbox(ctx *alps.Context) error {
|
||||||
mboxName, err := url.PathUnescape(ctx.Param("mbox"))
|
ibase, err := newIMAPBaseRenderData(ctx, alps.NewBaseRenderData(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mbox := ibase.Mailbox
|
||||||
|
title := mbox.Name
|
||||||
|
if title == "INBOX" {
|
||||||
|
title = "Inbox"
|
||||||
|
}
|
||||||
|
if mbox.Unseen > 0 {
|
||||||
|
title = fmt.Sprintf("(%d) %s", mbox.Unseen, title)
|
||||||
|
}
|
||||||
|
ibase.BaseRenderData.WithTitle(title)
|
||||||
|
|
||||||
page := 0
|
page := 0
|
||||||
if pageStr := ctx.QueryParam("page"); pageStr != "" {
|
if pageStr := ctx.QueryParam("page"); pageStr != "" {
|
||||||
var err error
|
var err error
|
||||||
|
@ -140,33 +193,20 @@ func handleGetMailbox(ctx *alps.Context) error {
|
||||||
|
|
||||||
query := ctx.QueryParam("query")
|
query := ctx.QueryParam("query")
|
||||||
|
|
||||||
var mailboxes []MailboxInfo
|
var (
|
||||||
var msgs []IMAPMessage
|
msgs []IMAPMessage
|
||||||
var mbox, inbox *MailboxStatus
|
total int
|
||||||
var total int
|
)
|
||||||
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
||||||
var err error
|
var err error
|
||||||
if mailboxes, err = listMailboxes(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if mbox, err = getMailboxStatus(c, mboxName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if query != "" {
|
if query != "" {
|
||||||
msgs, total, err = searchMessages(c, mboxName, query, page, messagesPerPage)
|
msgs, total, err = searchMessages(c, mbox.Name, query, page, messagesPerPage)
|
||||||
} else {
|
} else {
|
||||||
msgs, err = listMessages(c, mbox, page, messagesPerPage)
|
msgs, err = listMessages(c, mbox, page, messagesPerPage)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mboxName == "INBOX" {
|
|
||||||
inbox = mbox
|
|
||||||
} else {
|
|
||||||
if inbox, err = getMailboxStatus(c, "INBOX"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -190,23 +230,8 @@ func handleGetMailbox(ctx *alps.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title := mbox.Name
|
|
||||||
if title == "INBOX" {
|
|
||||||
title = "Inbox"
|
|
||||||
}
|
|
||||||
|
|
||||||
if mbox.Unseen > 0 {
|
|
||||||
title = fmt.Sprintf("(%d) %s", mbox.Unseen, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
categorized := categorizeMailboxes(mailboxes, inbox, mbox)
|
|
||||||
|
|
||||||
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{
|
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{
|
||||||
BaseRenderData: *alps.NewBaseRenderData(ctx).WithTitle(title),
|
IMAPBaseRenderData: *ibase,
|
||||||
Mailbox: mbox,
|
|
||||||
Inbox: inbox,
|
|
||||||
CategorizedMailboxes: categorized,
|
|
||||||
Mailboxes: mailboxes,
|
|
||||||
Messages: msgs,
|
Messages: msgs,
|
||||||
PrevPage: prevPage,
|
PrevPage: prevPage,
|
||||||
NextPage: nextPage,
|
NextPage: nextPage,
|
||||||
|
@ -262,10 +287,7 @@ func handleLogout(ctx *alps.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageRenderData struct {
|
type MessageRenderData struct {
|
||||||
alps.BaseRenderData
|
IMAPBaseRenderData
|
||||||
Mailboxes []MailboxInfo
|
|
||||||
Mailbox *MailboxStatus
|
|
||||||
Inbox *MailboxStatus
|
|
||||||
Message *IMAPMessage
|
Message *IMAPMessage
|
||||||
Part *IMAPPartNode
|
Part *IMAPPartNode
|
||||||
View interface{}
|
View interface{}
|
||||||
|
@ -274,10 +296,13 @@ type MessageRenderData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetPart(ctx *alps.Context, raw bool) error {
|
func handleGetPart(ctx *alps.Context, raw bool) error {
|
||||||
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
|
_, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
|
||||||
|
ibase, err := newIMAPBaseRenderData(ctx, alps.NewBaseRenderData(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err)
|
return err
|
||||||
}
|
}
|
||||||
|
mbox := ibase.Mailbox
|
||||||
|
|
||||||
partPath, err := parsePartPath(ctx.QueryParam("part"))
|
partPath, err := parsePartPath(ctx.QueryParam("part"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err)
|
return echo.NewHTTPError(http.StatusBadRequest, err)
|
||||||
|
@ -289,28 +314,13 @@ func handleGetPart(ctx *alps.Context, raw bool) error {
|
||||||
}
|
}
|
||||||
messagesPerPage := settings.MessagesPerPage
|
messagesPerPage := settings.MessagesPerPage
|
||||||
|
|
||||||
var mailboxes []MailboxInfo
|
|
||||||
var msg *IMAPMessage
|
var msg *IMAPMessage
|
||||||
var part *message.Entity
|
var part *message.Entity
|
||||||
var mbox, inbox *MailboxStatus
|
|
||||||
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
||||||
var err error
|
var err error
|
||||||
if mailboxes, err = listMailboxes(c); err != nil {
|
if msg, part, err = getMessagePart(c, mbox.Name, uid, partPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if msg, part, err = getMessagePart(c, mboxName, uid, partPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if mbox, err = getMailboxStatus(c, mboxName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if mboxName == "INBOX" {
|
|
||||||
inbox = mbox
|
|
||||||
} else {
|
|
||||||
if inbox, err = getMailboxStatus(c, "INBOX"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -367,22 +377,20 @@ func handleGetPart(ctx *alps.Context, raw bool) error {
|
||||||
flags[f] = msg.HasFlag(f)
|
flags[f] = msg.HasFlag(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ibase.BaseRenderData.WithTitle(msg.Envelope.Subject)
|
||||||
|
|
||||||
return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{
|
return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{
|
||||||
BaseRenderData: *alps.NewBaseRenderData(ctx).
|
IMAPBaseRenderData: *ibase,
|
||||||
WithTitle(msg.Envelope.Subject),
|
Message: msg,
|
||||||
Mailboxes: mailboxes,
|
Part: msg.PartByPath(partPath),
|
||||||
Mailbox: mbox,
|
View: view,
|
||||||
Inbox: inbox,
|
MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage,
|
||||||
Message: msg,
|
Flags: flags,
|
||||||
Part: msg.PartByPath(partPath),
|
|
||||||
View: view,
|
|
||||||
MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage,
|
|
||||||
Flags: flags,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComposeRenderData struct {
|
type ComposeRenderData struct {
|
||||||
alps.BaseRenderData
|
IMAPBaseRenderData
|
||||||
Message *OutgoingMessage
|
Message *OutgoingMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +446,11 @@ func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error {
|
func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error {
|
||||||
|
ibase, err := newIMAPBaseRenderData(ctx, alps.NewBaseRenderData(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') {
|
if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') {
|
||||||
msg.From = ctx.Session.Username()
|
msg.From = ctx.Session.Username()
|
||||||
}
|
}
|
||||||
|
@ -537,8 +550,8 @@ func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{
|
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{
|
||||||
BaseRenderData: *alps.NewBaseRenderData(ctx),
|
IMAPBaseRenderData: *ibase,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
{{template "head.html" .}}
|
{{template "head.html" .}}
|
||||||
{{template "nav.html" .}}
|
{{template "nav.html" .}}
|
||||||
|
{{template "util.html" .}}
|
||||||
|
|
||||||
<div class="page-wrap">
|
<div class="page-wrap">
|
||||||
<aside>
|
{{ template "aside" . }}
|
||||||
<a href="/compose" class="new active">Compose Mail</a>
|
|
||||||
<!-- TODO: use mailbox list from template data -->
|
|
||||||
<a href="/mailbox/INBOX">Inbox</a>
|
|
||||||
<a href="/mailbox/Drafts">Drafts</a>
|
|
||||||
<a href="/mailbox/Sent">Sent</a>
|
|
||||||
<a href="/mailbox/Archive">Archive</a>
|
|
||||||
<a href="/mailbox/Junk">Junk</a>
|
|
||||||
<a href="/mailbox/Trash">Trash</a>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<main class="create-update">
|
<main class="create-update">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{template "head.html" .}}
|
{{template "head.html" .}}
|
||||||
{{template "nav.html" .}}
|
{{template "nav.html" .}}
|
||||||
|
{{template "util.html" .}}
|
||||||
|
|
||||||
{{ define "mbox-link" }}
|
{{ define "mbox-link" }}
|
||||||
{{ if not (.HasAttr "\\Noselect") }}
|
{{ if not (.HasAttr "\\Noselect") }}
|
||||||
|
@ -21,25 +22,7 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<div class="page-wrap">
|
<div class="page-wrap">
|
||||||
<aside>
|
{{ template "aside" . }}
|
||||||
<!-- the logo image, dimensions 200x32 may be present or not -->
|
|
||||||
<a href="/compose" class="new">Compose Mail</a>
|
|
||||||
{{ with .CategorizedMailboxes }}
|
|
||||||
{{ with .Common.Inbox }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ with .Common.Drafts }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ with .Common.Sent }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ with .Common.Junk }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ with .Common.Trash }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ with .Common.Archive }}{{ template "mbox-link" . }}{{ end}}
|
|
||||||
{{ if .Additional }}
|
|
||||||
<hr />
|
|
||||||
{{ range .Additional }}
|
|
||||||
{{ template "mbox-link" . }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form id="messages-form" method="post"></form>
|
<form id="messages-form" method="post"></form>
|
||||||
<main class="message-list">
|
<main class="message-list">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{template "head.html" .}}
|
{{template "head.html" .}}
|
||||||
{{template "nav.html" .}}
|
{{template "nav.html" .}}
|
||||||
|
{{template "util.html" .}}
|
||||||
|
|
||||||
{{define "message-part-tree"}}
|
{{define "message-part-tree"}}
|
||||||
{{/* nested templates can't access the parent's context */}}
|
{{/* nested templates can't access the parent's context */}}
|
||||||
|
@ -28,31 +29,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="page-wrap">
|
<div class="page-wrap">
|
||||||
{{$current := .Mailbox}}
|
{{ $current := .Mailbox }}
|
||||||
<aside>
|
{{ template "aside" . }}
|
||||||
<!-- the logo image, dimensions 200x32 may be present or not -->
|
|
||||||
<a href="/compose" class="new">Compose Mail</a>
|
|
||||||
{{range .Mailboxes}}
|
|
||||||
<a href="{{.URL}}"
|
|
||||||
{{ if eq $current.Name .Name }}class="active"{{ end }}>
|
|
||||||
{{ if eq .Name "INBOX" }}
|
|
||||||
Inbox
|
|
||||||
{{else}}
|
|
||||||
{{.Name}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{ $unseen := 0 }}
|
|
||||||
{{ if eq .Name "INBOX" }}
|
|
||||||
{{ $unseen = $.Inbox.Unseen }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq .Name $.Mailbox.Name }}
|
|
||||||
{{ $unseen = $.Mailbox.Unseen }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if $unseen }}({{ $unseen }}){{ end }}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<main class="message">
|
<main class="message">
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
|
|
41
themes/alps/util.html
Normal file
41
themes/alps/util.html
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{{ define "mbox-link" }}
|
||||||
|
{{ if not (.HasAttr "\\Noselect") }}
|
||||||
|
<a href="{{.URL}}" {{ if .Active }}class="active"{{ end }}>
|
||||||
|
{{- if eq .Name "INBOX" -}}
|
||||||
|
Inbox
|
||||||
|
{{- else -}}
|
||||||
|
{{ .Name }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if .HasAttr "\\HasChildren" }}/{{ end }}
|
||||||
|
|
||||||
|
{{ if and (ne .Unseen -1) (ne .Unseen 0) }}({{ .Unseen }}){{ end }}
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<span class="noselect">
|
||||||
|
{{.Name}}{{- if .HasAttr "\\HasChildren" }}/{{ end }}
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "aside" }}
|
||||||
|
<aside>
|
||||||
|
<!-- the logo image, dimensions 200x32 may be present or not -->
|
||||||
|
<a href="/compose" class="new
|
||||||
|
{{ if eq $.GlobalData.URL.Path "/compose" }}active{{ end }}
|
||||||
|
">Compose Mail</a>
|
||||||
|
{{ with .CategorizedMailboxes }}
|
||||||
|
{{ with .Common.Inbox }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ with .Common.Drafts }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ with .Common.Sent }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ with .Common.Junk }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ with .Common.Trash }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ with .Common.Archive }}{{ template "mbox-link" . }}{{ end}}
|
||||||
|
{{ if .Additional }}
|
||||||
|
<hr />
|
||||||
|
{{ range .Additional }}
|
||||||
|
{{ template "mbox-link" . }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</aside>
|
||||||
|
{{ end }}
|
Loading…
Reference in a new issue