diff options
-rw-r--r-- | config/columns.go | 4 | ||||
-rw-r--r-- | config/templates.go | 48 | ||||
-rw-r--r-- | config/ui_test.go | 31 | ||||
-rw-r--r-- | lib/state/templates.go (renamed from lib/templates/data.go) | 193 | ||||
-rw-r--r-- | lib/templates/functions.go | 13 | ||||
-rw-r--r-- | lib/templates/template.go | 19 | ||||
-rw-r--r-- | models/templates.go | 36 | ||||
-rw-r--r-- | widgets/account.go | 27 | ||||
-rw-r--r-- | widgets/compose.go | 60 | ||||
-rw-r--r-- | widgets/msglist.go | 24 |
10 files changed, 223 insertions, 232 deletions
diff --git a/config/columns.go b/config/columns.go index b1c7191b..55efa2a7 100644 --- a/config/columns.go +++ b/config/columns.go @@ -90,9 +90,7 @@ func parseColumnDef(col string, section *ini.Section) (*ColumnDef, error) { return nil, err } - data := templates.DummyData() - var buf bytes.Buffer - err = t.Execute(&buf, data) + err = templates.Render(t, &bytes.Buffer{}, &dummyData{}) if err != nil { return nil, err } diff --git a/config/templates.go b/config/templates.go index 5580c056..f9dda0ea 100644 --- a/config/templates.go +++ b/config/templates.go @@ -3,9 +3,11 @@ package config import ( "path" "strings" + "time" "git.sr.ht/~rjarry/aerc/lib/templates" "git.sr.ht/~rjarry/aerc/log" + "github.com/emersion/go-message/mail" "github.com/go-ini/ini" ) @@ -48,13 +50,13 @@ func parseTemplates(file *ini.File) error { // we want to fail during startup if the templates are not ok // hence we do dummy executes here t := Templates - if err := templates.CheckTemplate(t.NewMessage, t.TemplateDirs); err != nil { + if err := checkTemplate(t.NewMessage, t.TemplateDirs); err != nil { return err } - if err := templates.CheckTemplate(t.QuotedReply, t.TemplateDirs); err != nil { + if err := checkTemplate(t.QuotedReply, t.TemplateDirs); err != nil { return err } - if err := templates.CheckTemplate(t.Forwards, t.TemplateDirs); err != nil { + if err := checkTemplate(t.Forwards, t.TemplateDirs); err != nil { return err } @@ -62,3 +64,43 @@ func parseTemplates(file *ini.File) error { return nil } + +func checkTemplate(filename string, dirs []string) error { + var data dummyData + _, err := templates.ParseTemplateFromFile(filename, dirs, &data) + return err +} + +// only for validation +type dummyData struct{} + +var ( + addr1 = mail.Address{Name: "John Foo", Address: "foo@bar.org"} + addr2 = mail.Address{Name: "John Bar", Address: "bar@foo.org"} +) + +func (d *dummyData) Account() string { return "work" } +func (d *dummyData) Folder() string { return "INBOX" } +func (d *dummyData) To() []*mail.Address { return []*mail.Address{&addr1} } +func (d *dummyData) Cc() []*mail.Address { return nil } +func (d *dummyData) Bcc() []*mail.Address { return nil } +func (d *dummyData) From() []*mail.Address { return []*mail.Address{&addr2} } +func (d *dummyData) Peer() []*mail.Address { return d.From() } +func (d *dummyData) ReplyTo() []*mail.Address { return nil } +func (d *dummyData) Date() time.Time { return time.Now() } +func (d *dummyData) DateAutoFormat(time.Time) string { return "" } +func (d *dummyData) Header(string) string { return "" } +func (d *dummyData) Subject() string { return "[PATCH] hey" } +func (d *dummyData) Number() int { return 0 } +func (d *dummyData) Labels() []string { return nil } +func (d *dummyData) Flags() []string { return nil } +func (d *dummyData) MessageId() string { return "123456789@foo.org" } +func (d *dummyData) Size() int { return 420 } +func (d *dummyData) OriginalText() string { return "Blah blah blah" } +func (d *dummyData) OriginalDate() time.Time { return time.Now() } +func (d *dummyData) OriginalFrom() []*mail.Address { return d.From() } +func (d *dummyData) OriginalMIMEType() string { return "text/plain" } +func (d *dummyData) OriginalHeader(string) string { return "" } +func (d *dummyData) Recent() int { return 1 } +func (d *dummyData) Unread() int { return 3 } +func (d *dummyData) Exists() int { return 14 } diff --git a/config/ui_test.go b/config/ui_test.go index c5eb6031..677dcc86 100644 --- a/config/ui_test.go +++ b/config/ui_test.go @@ -1,13 +1,20 @@ package config import ( - "bytes" + "reflect" "testing" + "text/template" - "git.sr.ht/~rjarry/aerc/lib/templates" "github.com/stretchr/testify/assert" ) +func templateText(t *template.Template) string { + // unfortunately, the original template text is stored as a private + // field, for test purposes, access its value via reflection + v := reflect.ValueOf(t).Elem() + return v.FieldByName("text").String() +} + func TestConvertIndexFormat(t *testing.T) { columns, err := convertIndexFormat("%-20.20D %-17.17n %Z %s") if err != nil { @@ -15,32 +22,26 @@ func TestConvertIndexFormat(t *testing.T) { } assert.Len(t, columns, 4) - data := templates.DummyData() - var buf bytes.Buffer - assert.Equal(t, "date", columns[0].Name) assert.Equal(t, 20.0, columns[0].Width) assert.Equal(t, ALIGN_LEFT|WIDTH_EXACT, columns[0].Flags) - assert.Nil(t, columns[0].Template.Execute(&buf, data)) + assert.Equal(t, `{{.DateAutoFormat .Date.Local}}`, + templateText(columns[0].Template)) - buf.Reset() assert.Equal(t, "name", columns[1].Name) assert.Equal(t, 17.0, columns[1].Width) assert.Equal(t, ALIGN_LEFT|WIDTH_EXACT, columns[1].Flags) - assert.Nil(t, columns[1].Template.Execute(&buf, data)) - assert.Equal(t, "John Doe", buf.String()) + assert.Equal(t, `{{index (.From | names) 0}}`, + templateText(columns[1].Template)) - buf.Reset() assert.Equal(t, "flags", columns[2].Name) assert.Equal(t, 4.0, columns[2].Width) assert.Equal(t, ALIGN_RIGHT|WIDTH_EXACT, columns[2].Flags) - assert.Nil(t, columns[2].Template.Execute(&buf, data)) - assert.Equal(t, "O!*", buf.String()) + assert.Equal(t, `{{.Flags | join ""}}`, + templateText(columns[2].Template)) - buf.Reset() assert.Equal(t, "subject", columns[3].Name) assert.Equal(t, 0.0, columns[3].Width) assert.Equal(t, ALIGN_LEFT|WIDTH_AUTO, columns[3].Flags) - assert.Nil(t, columns[3].Template.Execute(&buf, data)) - assert.Equal(t, "[PATCH aerc 2/3] foo: baz bar buz", buf.String()) + assert.Equal(t, `{{.Subject}}`, templateText(columns[3].Template)) } diff --git a/lib/templates/data.go b/lib/state/templates.go index e34dc2f3..fdf662c5 100644 --- a/lib/templates/data.go +++ b/lib/state/templates.go @@ -1,10 +1,10 @@ -package templates +package state import ( - "fmt" "strings" "time" + "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/models" sortthread "github.com/emersion/go-imap-sortthread" "github.com/emersion/go-message/mail" @@ -24,44 +24,12 @@ type TemplateData struct { ThreadSameSubject bool ThreadPrefix string - // account config + // selected account + account *config.AccountConfig myAddresses map[string]bool - account string folder string // selected folder name - - // ui config - timeFmt string - thisDayTimeFmt string - thisWeekTimeFmt string - thisYearTimeFmt string - iconAttachment string -} - -func NewTemplateData( - from *mail.Address, - aliases []*mail.Address, - account string, - folder string, - timeFmt string, - thisDayTimeFmt string, - thisWeekTimeFmt string, - thisYearTimeFmt string, - iconAttachment string, -) *TemplateData { - myAddresses := map[string]bool{from.Address: true} - for _, addr := range aliases { - myAddresses[addr.Address] = true - } - return &TemplateData{ - myAddresses: myAddresses, - account: account, - folder: folder, - timeFmt: timeFmt, - thisDayTimeFmt: thisDayTimeFmt, - thisWeekTimeFmt: thisWeekTimeFmt, - thisYearTimeFmt: thisYearTimeFmt, - iconAttachment: iconAttachment, - } + folders []string + getRUEcount func(string) (int, int, int) } // only used for compose/reply/forward @@ -77,14 +45,38 @@ func (d *TemplateData) SetInfo(info *models.MessageInfo, num int, marked bool) { d.marked = marked } +func (d *TemplateData) SetAccount(acct *config.AccountConfig) { + d.account = acct + d.myAddresses = map[string]bool{acct.From.Address: true} + for _, addr := range acct.Aliases { + d.myAddresses[addr.Address] = true + } +} + +func (d *TemplateData) SetFolder(folder string) { + d.folder = folder +} + +func (d *TemplateData) SetRUE(folders []string, cb func(string) (int, int, int)) { + d.folders = folders + d.getRUEcount = cb +} + func (d *TemplateData) Account() string { - return d.account + if d.account != nil { + return d.account.Name + } + return "" } func (d *TemplateData) Folder() string { return d.folder } +func (d *TemplateData) ui() *config.UIConfig { + return config.Ui.ForAccount(d.Account()).ForFolder(d.folder) +} + func (d *TemplateData) To() []*mail.Address { var to []*mail.Address switch { @@ -175,20 +167,21 @@ func (d *TemplateData) DateAutoFormat(date time.Time) string { if date.IsZero() { return "" } + ui := d.ui() year := date.Year() day := date.YearDay() now := time.Now() thisYear := now.Year() thisDay := now.YearDay() - fmt := d.timeFmt + fmt := ui.TimestampFormat if year == thisYear { switch { - case day == thisDay && d.thisDayTimeFmt != "": - fmt = d.thisDayTimeFmt - case day > thisDay-7 && d.thisWeekTimeFmt != "": - fmt = d.thisWeekTimeFmt - case d.thisYearTimeFmt != "": - fmt = d.thisYearTimeFmt + case day == thisDay && ui.ThisDayTimeFormat != "": + fmt = ui.ThisDayTimeFormat + case day > thisDay-7 && ui.ThisWeekTimeFormat != "": + fmt = ui.ThisWeekTimeFormat + case ui.ThisYearTimeFormat != "": + fmt = ui.ThisYearTimeFormat } } return date.Format(fmt) @@ -230,8 +223,8 @@ func (d *TemplateData) SubjectBase() string { return base } -func (d *TemplateData) Number() string { - return fmt.Sprintf("%d", d.msgNum) +func (d *TemplateData) Number() int { + return d.msgNum } func (d *TemplateData) Labels() []string { @@ -263,7 +256,7 @@ func (d *TemplateData) Flags() []string { if d.info.BodyStructure != nil { for _, bS := range d.info.BodyStructure.Parts { if strings.ToLower(bS.Disposition) == "attachment" { - flags = append(flags, d.iconAttachment) + flags = append(flags, d.ui().IconAttachment) break } } @@ -284,11 +277,11 @@ func (d *TemplateData) MessageId() string { return d.info.Envelope.MessageId } -func (d *TemplateData) Size() uint32 { +func (d *TemplateData) Size() int { if d.info == nil || d.info.Envelope == nil { return 0 } - return d.info.Size + return int(d.info.Size) } func (d *TemplateData) OriginalText() string { @@ -331,76 +324,30 @@ func (d *TemplateData) OriginalHeader(name string) string { return text } -// DummyData provides dummy data to test template validity -func DummyData() *TemplateData { - from := &mail.Address{ - Name: "John Doe", - Address: "john@example.com", - } - to := &mail.Address{ - Name: "Alice Doe", - Address: "alice@example.com", - } - h := &mail.Header{} - h.SetAddressList("from", []*mail.Address{from}) - h.SetAddressList("to", []*mail.Address{to}) - - oh := &mail.Header{} - oh.SetAddressList("from", []*mail.Address{to}) - oh.SetAddressList("to", []*mail.Address{from}) - - original := models.OriginalMail{ - Date: time.Now(), - From: from.String(), - Text: "This is only a test text", - MIMEType: "text/plain", - RFC822Headers: oh, - } - data := NewTemplateData( - to, - nil, - "account", - "folder", - "2006 Jan 02, 15:04 GMT-0700", - "15:04", - "Monday 15:04", - "Jan 02", - "a", - ) - data.SetHeaders(h, &original) - - info := &models.MessageInfo{ - BodyStructure: &models.BodyStructure{ - MIMEType: "text", - MIMESubType: "plain", - Params: make(map[string]string), - Description: "", - Encoding: "", - Parts: []*models.BodyStructure{}, - Disposition: "", - DispositionParams: make(map[string]string), - }, - Envelope: &models.Envelope{ - Date: time.Date(1981, 6, 23, 16, 52, 0, 0, time.UTC), - Subject: "[PATCH aerc 2/3] foo: baz bar buz", - From: []*mail.Address{from}, - ReplyTo: []*mail.Address{}, - To: []*mail.Address{to}, - Cc: []*mail.Address{}, - Bcc: []*mail.Address{}, - MessageId: "", - InReplyTo: "", - }, - Flags: models.FlaggedFlag, - Labels: []string{"inbox", "patch"}, - InternalDate: time.Now(), - RFC822Headers: nil, - Refs: []string{}, - Size: 65512, - Uid: 12345, - Error: nil, - } - data.SetInfo(info, 42, true) - - return data +func (d *TemplateData) rue() (int, int, int) { + var recent, unread, exists int + if d.getRUEcount != nil { + for _, dir := range d.folders { + r, u, e := d.getRUEcount(dir) + recent += r + unread += u + exists += e + } + } + return recent, unread, exists +} + +func (d *TemplateData) Recent() int { + r, _, _ := d.rue() + return r +} + +func (d *TemplateData) Unread() int { + _, u, _ := d.rue() + return u +} + +func (d *TemplateData) Exists() int { + _, _, e := d.rue() + return e } diff --git a/lib/templates/functions.go b/lib/templates/functions.go index 630db264..37b014f5 100644 --- a/lib/templates/functions.go +++ b/lib/templates/functions.go @@ -161,9 +161,14 @@ func persons(addresses []*mail.Address) []string { var units = []string{"K", "M", "G", "T"} -func humanReadable(value uint32) string { +func humanReadable(value int) string { + sign := "" + if value < 0 { + sign = "-" + value = -value + } if value < 1000 { - return fmt.Sprintf("%d", value) + return fmt.Sprintf("%s%d", sign, value) } val := float64(value) unit := "" @@ -172,9 +177,9 @@ func humanReadable(value uint32) string { val /= 1000.0 } if val < 100.0 { - return fmt.Sprintf("%.1f%s", val, unit) + return fmt.Sprintf("%s%.1f%s", sign, val, unit) } - return fmt.Sprintf("%.0f%s", val, unit) + return fmt.Sprintf("%s%.0f%s", sign, val, unit) } func cwd() string { diff --git a/lib/templates/template.go b/lib/templates/template.go index 28785356..bf257669 100644 --- a/lib/templates/template.go +++ b/lib/templates/template.go @@ -8,6 +8,7 @@ import ( "path" "text/template" + "git.sr.ht/~rjarry/aerc/models" "github.com/mitchellh/go-homedir" ) @@ -28,19 +29,21 @@ func findTemplate(templateName string, templateDirs []string) (string, error) { "Can't find template %q in any of %v ", templateName, templateDirs) } -func ParseTemplateFromFile(templateName string, templateDirs []string, data interface{}) (io.Reader, error) { - templateFile, err := findTemplate(templateName, templateDirs) +func ParseTemplateFromFile( + name string, dirs []string, data models.TemplateData, +) (io.Reader, error) { + templateFile, err := findTemplate(name, dirs) if err != nil { return nil, err } - emailTemplate, err := template.New(templateName). + emailTemplate, err := template.New(name). Funcs(templateFuncs).ParseFiles(templateFile) if err != nil { return nil, err } var body bytes.Buffer - if err := emailTemplate.Execute(&body, data); err != nil { + if err := Render(emailTemplate, &body, data); err != nil { return nil, err } return &body, nil @@ -50,10 +53,6 @@ func ParseTemplate(name, content string) (*template.Template, error) { return template.New(name).Funcs(templateFuncs).Parse(content) } -func CheckTemplate(templateName string, templateDirs []string) error { - if templateName != "" { - _, err := ParseTemplateFromFile(templateName, templateDirs, DummyData()) - return err - } - return nil +func Render(t *template.Template, w io.Writer, data models.TemplateData) error { + return t.Execute(w, data) } diff --git a/models/templates.go b/models/templates.go new file mode 100644 index 00000000..84f68c21 --- /dev/null +++ b/models/templates.go @@ -0,0 +1,36 @@ +package models + +import ( + "time" + + "github.com/emersion/go-message/mail" +) + +// This interface needs to be implemented for compliance with aerc-templates(7) +type TemplateData interface { + Account() string + Folder() string + To() []*mail.Address + Cc() []*mail.Address + Bcc() []*mail.Address + From() []*mail.Address + Peer() []*mail.Address + ReplyTo() []*mail.Address + Date() time.Time + DateAutoFormat(date time.Time) string + Header(name string) string + Subject() string + Number() int + Labels() []string + Flags() []string + MessageId() string + Size() int + OriginalText() string + OriginalDate() time.Time + OriginalFrom() []*mail.Address + OriginalMIMEType() string + OriginalHeader(name string) string + Recent() int + Unread() int + Exists() int +} diff --git a/widgets/account.go b/widgets/account.go index 85043d1f..3176a2bf 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -13,7 +13,9 @@ import ( "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib/marker" "git.sr.ht/~rjarry/aerc/lib/sort" + "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/lib/statusline" + "git.sr.ht/~rjarry/aerc/lib/templates" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/models" @@ -597,23 +599,14 @@ func (acct *AccountView) Vsplit(n int) error { // setTitle executes the title template and sets the tab title func (acct *AccountView) setTitle() { - data := struct { - Account string - Recent int - Unread int - Exists int - Folder string - }{} - data.Account = acct.Name() - data.Folder = acct.SelectedDirectory() - for _, name := range acct.dirlist.List() { - r, u, e := acct.dirlist.GetRUECount(name) - data.Recent += r - data.Unread += u - data.Exists += e - } - buf := bytes.NewBuffer(nil) - err := acct.uiConf.TabTitleAccount.Execute(buf, data) + var data state.TemplateData + + data.SetAccount(acct.acct) + data.SetFolder(acct.SelectedDirectory()) + data.SetRUE(acct.dirlist.List(), acct.dirlist.GetRUECount) + + var buf bytes.Buffer + err := templates.Render(acct.uiConf.TabTitleAccount, &buf, &data) if err != nil { acct.PushError(err) return diff --git a/widgets/compose.go b/widgets/compose.go index 7311de7a..b74e1537 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -23,6 +23,7 @@ import ( "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib/format" + "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/lib/templates" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/log" @@ -95,21 +96,11 @@ func NewComposer( completer: nil, } - uiConfig := acct.UiConfig() - - templateData := templates.NewTemplateData( - acct.acct.From, - acct.acct.Aliases, - acct.Name(), - acct.Directories().Selected(), - uiConfig.MessageViewTimestampFormat, - uiConfig.MessageViewThisDayTimeFormat, - uiConfig.MessageViewThisWeekTimeFormat, - uiConfig.MessageViewThisYearTimeFormat, - uiConfig.IconAttachment, - ) - templateData.SetHeaders(h, orig) - if err := c.AddTemplate(template, templateData); err != nil { + var data state.TemplateData + data.SetAccount(acct.acct) + data.SetFolder(acct.Directories().Selected()) + data.SetHeaders(h, orig) + if err := c.AddTemplate(template, &data); err != nil { return nil, err } c.AddSignature() @@ -509,7 +500,7 @@ func (c *Composer) RemovePart(mimetype string) error { return fmt.Errorf("%s part not found", mimetype) } -func (c *Composer) AddTemplate(template string, data interface{}) error { +func (c *Composer) AddTemplate(template string, data models.TemplateData) error { if template == "" { return nil } @@ -1551,37 +1542,24 @@ func (c *Composer) setTitle() { if c.Tab == nil { return } - data := struct { - Account string - Subject string - To []*mail.Address - From []*mail.Address - Cc []*mail.Address - Bcc []*mail.Address - OriginalFrom []*mail.Address - }{} - data.Account = c.acct.Name() + + var data state.TemplateData + + header := c.header.Copy() // Get subject direct from the textinput subject, ok := c.editors["subject"] if ok { - data.Subject = subject.input.String() - } - if data.Subject == "" { - data.Subject = "New Email" + header.SetSubject(subject.input.String()) } - // Get address fields from header, which gets updated on focus lost of - // any headerEditor field - data.From, _ = c.header.AddressList("from") - data.To, _ = c.header.AddressList("to") - data.Cc, _ = c.header.AddressList("cc") - data.Bcc, _ = c.header.AddressList("bcc") - - if c.parent != nil && c.parent.RFC822Headers != nil { - data.OriginalFrom, _ = c.parent.RFC822Headers.AddressList("from") + if header.Get("subject") == "" { + header.SetSubject("New Email") } + data.SetAccount(c.acctConfig) + data.SetFolder(c.acct.SelectedDirectory()) + data.SetHeaders(&header, c.parent) - buf := bytes.NewBuffer(nil) - err := c.acct.UiConfig().TabTitleComposer.Execute(buf, data) + var buf bytes.Buffer + err := templates.Render(c.acct.UiConfig().TabTitleComposer, &buf, &data) if err != nil { c.acct.PushError(err) return diff --git a/widgets/msglist.go b/widgets/msglist.go index 85072d1e..4b2bf721 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -11,7 +11,7 @@ import ( "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib/iterator" - "git.sr.ht/~rjarry/aerc/lib/templates" + "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/models" @@ -87,19 +87,11 @@ func (ml *MessageList) Draw(ctx *ui.Context) { return } - data := templates.NewTemplateData( - acct.acct.From, - acct.acct.Aliases, - acct.Name(), - acct.Directories().Selected(), - uiConfig.TimestampFormat, - uiConfig.ThisDayTimeFormat, - uiConfig.ThisWeekTimeFormat, - uiConfig.ThisYearTimeFormat, - uiConfig.IconAttachment, - ) - var needsHeaders []uint32 + var data state.TemplateData + + data.SetAccount(acct.acct) + data.SetFolder(acct.Directories().Selected()) customDraw := func(t *ui.Table, r int, c *ui.Context) bool { row := &t.Rows[r] @@ -175,7 +167,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { lastSubject = baseSubject prevThread = thread - if addMessage(store, thread.Uid, &table, data, uiConfig) { + if addMessage(store, thread.Uid, &table, &data, uiConfig) { break threadLoop } } @@ -187,7 +179,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { continue } uid := iter.Value().(uint32) - if addMessage(store, uid, &table, data, uiConfig) { + if addMessage(store, uid, &table, &data, uiConfig) { break } } @@ -220,7 +212,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { func addMessage( store *lib.MessageStore, uid uint32, - table *ui.Table, data *templates.TemplateData, + table *ui.Table, data *state.TemplateData, uiConfig *config.UIConfig, ) bool { msg := store.Messages[uid] |