Display user specified headers in viewer if present

This commit is contained in:
Daniel Bridges 2019-07-15 11:56:44 -07:00 committed by Drew DeVault
parent d7975132b6
commit dfc048fe28
5 changed files with 116 additions and 41 deletions

View file

@ -61,6 +61,14 @@ alternatives=text/plain,text/html
# Default: false # Default: false
show-headers=false show-headers=false
#
# Layout of headers when viewing a message. To display multiple headers in the
# same row, separate them with a pipe, e.g. "From|To". Rows will be hidden if
# none of their specified headers are present in the message.
#
# Default: From|To,Cc|Bcc,Date,Subject
header-layout=From|To,Cc|Bcc,Date,Subject
[compose] [compose]
# #
# Specifies the command to run the editor with. It will be shown in an embedded # Specifies the command to run the editor with. It will be shown in an embedded

View file

@ -80,6 +80,7 @@ type ViewerConfig struct {
Pager string Pager string
Alternatives []string Alternatives []string
ShowHeaders bool `ini:"show-headers"` ShowHeaders bool `ini:"show-headers"`
HeaderLayout [][]string `ini:"-"`
} }
type AercConfig struct { type AercConfig struct {
@ -261,6 +262,8 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
switch key { switch key {
case "alternatives": case "alternatives":
config.Viewer.Alternatives = strings.Split(val, ",") config.Viewer.Alternatives = strings.Split(val, ",")
case "header-layout":
config.Viewer.HeaderLayout = parseLayout(val)
} }
} }
} }
@ -323,6 +326,18 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
EmptyDirlist: "(no folders)", EmptyDirlist: "(no folders)",
MouseEnabled: false, MouseEnabled: false,
}, },
Viewer: ViewerConfig{
Pager: "less -R",
Alternatives: []string{"text/plain", "text/html"},
ShowHeaders: false,
HeaderLayout: [][]string{
{"From", "To"},
{"Cc", "Bcc"},
{"Date"},
{"Subject"},
},
},
} }
// These bindings are not configurable // These bindings are not configurable
config.Bindings.AccountWizard.ExKey = KeyStroke{ config.Bindings.AccountWizard.ExKey = KeyStroke{
@ -431,3 +446,12 @@ func checkConfigPerms(filename string) error {
} }
return nil return nil
} }
func parseLayout(layout string) [][]string {
rows := strings.Split(layout, ",")
l := make([][]string, len(rows))
for i, r := range rows {
l[i] = strings.Split(r, "|")
}
return l
}

View file

@ -119,6 +119,14 @@ These options are configured in the *[viewer]* section of aerc.conf.
Default: text/plain,text/html Default: text/plain,text/html
*header-layout*
Defines the default headers to display when viewing a message. To display
multiple headers in the same row, separate them with a pipe, e.g. "From|To".
Rows will be hidden if none of their specified headers are present in the
message.
Default: From|To,Cc|Bcc,Date,Subject
*show-headers* *show-headers*
Default setting to determine whether to show full headers or only parsed Default setting to determine whether to show full headers or only parsed
ones in message viewer. ones in message viewer.

View file

@ -54,6 +54,20 @@ func NewGrid() *Grid {
return &Grid{invalid: true} return &Grid{invalid: true}
} }
// MakeGrid creates a grid with the specified number of columns and rows. Each
// cell has a size of 1.
func MakeGrid(numRows, numCols, rowStrategy, colStrategy int) *Grid {
rows := make([]GridSpec, numRows)
for i := 0; i < numRows; i++ {
rows[i] = GridSpec{rowStrategy, 1}
}
cols := make([]GridSpec, numCols)
for i := 0; i < numCols; i++ {
cols[i] = GridSpec{colStrategy, 1}
}
return NewGrid().Rows(rows).Columns(cols)
}
func (cell *GridCell) At(row, col int) *GridCell { func (cell *GridCell) At(row, col int) *GridCell {
cell.Row = row cell.Row = row
cell.Column = col cell.Column = col

View file

@ -45,53 +45,26 @@ type PartSwitcher struct {
func NewMessageViewer(acct *AccountView, conf *config.AercConfig, func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
store *lib.MessageStore, msg *models.MessageInfo) *MessageViewer { store *lib.MessageStore, msg *models.MessageInfo) *MessageViewer {
header, headerHeight := createHeader(msg, conf.Viewer.HeaderLayout)
grid := ui.NewGrid().Rows([]ui.GridSpec{ grid := ui.NewGrid().Rows([]ui.GridSpec{
{ui.SIZE_EXACT, 4}, // TODO: Based on number of header rows {ui.SIZE_EXACT, headerHeight},
{ui.SIZE_WEIGHT, 1}, {ui.SIZE_WEIGHT, 1},
}).Columns([]ui.GridSpec{ }).Columns([]ui.GridSpec{
{ui.SIZE_WEIGHT, 1}, {ui.SIZE_WEIGHT, 1},
}) })
// TODO: let user specify additional headers to show by default
headers := ui.NewGrid().Rows([]ui.GridSpec{
{ui.SIZE_EXACT, 1},
{ui.SIZE_EXACT, 1},
{ui.SIZE_EXACT, 1},
{ui.SIZE_EXACT, 1},
}).Columns([]ui.GridSpec{
{ui.SIZE_WEIGHT, 1},
{ui.SIZE_WEIGHT, 1},
})
headers.AddChild(
&HeaderView{
Name: "From",
Value: models.FormatAddresses(msg.Envelope.From),
}).At(0, 0)
headers.AddChild(
&HeaderView{
Name: "To",
Value: models.FormatAddresses(msg.Envelope.To),
}).At(0, 1)
headers.AddChild(
&HeaderView{
Name: "Date",
Value: msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"),
}).At(1, 0).Span(1, 2)
headers.AddChild(
&HeaderView{
Name: "Subject",
Value: msg.Envelope.Subject,
}).At(2, 0).Span(1, 2)
headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2)
switcher := &PartSwitcher{} switcher := &PartSwitcher{}
err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders) err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders)
if err != nil { if err != nil {
goto handle_error return &MessageViewer{
err: err,
grid: grid,
msg: msg,
}
} }
grid.AddChild(headers).At(0, 0) grid.AddChild(header).At(0, 0)
grid.AddChild(switcher).At(1, 0) grid.AddChild(switcher).At(1, 0)
return &MessageViewer{ return &MessageViewer{
@ -102,12 +75,60 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
store: store, store: store,
switcher: switcher, switcher: switcher,
} }
}
handle_error: func createHeader(msg *models.MessageInfo, layout [][]string) (grid *ui.Grid, height int) {
return &MessageViewer{ presentHeaders := presentHeaders(msg, layout)
err: err, rowCount := len(presentHeaders) + 1 // extra row for spacer
grid: grid, grid = ui.MakeGrid(rowCount, 1, ui.SIZE_EXACT, ui.SIZE_WEIGHT)
msg: msg, for i, cols := range presentHeaders {
r := ui.MakeGrid(1, len(cols), ui.SIZE_EXACT, ui.SIZE_WEIGHT)
for j, col := range cols {
r.AddChild(
&HeaderView{
Name: col,
Value: fmtHeader(msg, col),
}).At(0, j)
}
grid.AddChild(r).At(i, 0)
}
grid.AddChild(ui.NewFill(' ')).At(rowCount-1, 0)
return grid, rowCount
}
// presentHeaders returns a filtered header layout, removing rows whose headers
// do not appear in the provided message.
func presentHeaders(msg *models.MessageInfo, layout [][]string) [][]string {
headers := msg.RFC822Headers
result := make([][]string, 0, len(layout))
for _, row := range layout {
// To preserve layout alignment, only hide rows if all columns are empty
for _, col := range row {
if headers.Get(col) != "" {
result = append(result, row)
break
}
}
}
return result
}
func fmtHeader(msg *models.MessageInfo, header string) string {
switch header {
case "From":
return models.FormatAddresses(msg.Envelope.From)
case "To":
return models.FormatAddresses(msg.Envelope.To)
case "Cc":
return models.FormatAddresses(msg.Envelope.Cc)
case "Bcc":
return models.FormatAddresses(msg.Envelope.Bcc)
case "Date":
return msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM")
case "Subject":
return msg.Envelope.Subject
default:
return msg.RFC822Headers.Get(header)
} }
} }