Refactor template functions.

- Better template function shorthand substitution.
- Make `UnsubscribeURL` a function consitent with TrackLink.
  This is a breaking change that makes the old `.UnsubscrbeURL`
  obsolete.
This commit is contained in:
Kailash Nadh 2019-12-06 22:40:30 +05:30
parent 9a88c2ed7b
commit 4abcb2852c
4 changed files with 51 additions and 31 deletions

View File

@ -63,7 +63,7 @@
</div> </div>
<div class="footer" style="text-align: center;font-size: 12px;color: #888;"> <div class="footer" style="text-align: center;font-size: 12px;color: #888;">
<p>Don't want to receive these e-mails? <a href="{{ .UnsubscribeURL }}" style="color: #888;">Unsubscribe</a></p> <p>Don't want to receive these e-mails? <a href="{{ UnsubscribeURL }}" style="color: #888;">Unsubscribe</a></p>
<p>Powered by <a href="https://listmonk.app" target="_blank" style="color: #888;">listmonk</a></p> <p>Powered by <a href="https://listmonk.app" target="_blank" style="color: #888;">listmonk</a></p>
</div> </div>
<div class="gutter" style="padding: 30px;">&nbsp;{{ TrackView }}</div> <div class="gutter" style="padding: 30px;">&nbsp;{{ TrackView }}</div>

View File

@ -313,7 +313,7 @@ func main() {
FromEmail: app.Constants.FromEmail, FromEmail: app.Constants.FromEmail,
// url.com/unsubscribe/{campaign_uuid}/{subscriber_uuid} // url.com/unsubscribe/{campaign_uuid}/{subscriber_uuid}
UnsubscribeURL: fmt.Sprintf("%s/subscription/%%s/%%s", app.Constants.RootURL), UnsubURL: fmt.Sprintf("%s/subscription/%%s/%%s", app.Constants.RootURL),
// url.com/link/{campaign_uuid}/{subscriber_uuid}/{link_uuid} // url.com/link/{campaign_uuid}/{subscriber_uuid}/{link_uuid}
LinkTrackURL: fmt.Sprintf("%s/link/%%s/%%s/%%s", app.Constants.RootURL), LinkTrackURL: fmt.Sprintf("%s/link/%%s/%%s/%%s", app.Constants.RootURL),

View File

@ -61,8 +61,9 @@ type Manager struct {
type Message struct { type Message struct {
Campaign *models.Campaign Campaign *models.Campaign
Subscriber *models.Subscriber Subscriber *models.Subscriber
UnsubscribeURL string
Body []byte Body []byte
unsubURL string
from string from string
to string to string
} }
@ -74,7 +75,7 @@ type Config struct {
RequeueOnError bool RequeueOnError bool
FromEmail string FromEmail string
LinkTrackURL string LinkTrackURL string
UnsubscribeURL string UnsubURL string
ViewTrackURL string ViewTrackURL string
} }
@ -104,11 +105,12 @@ func New(cfg Config, src DataSource, notifCB models.AdminNotifCallback, l *log.L
// to message templates while they're compiled. // to message templates while they're compiled.
func (m *Manager) NewMessage(c *models.Campaign, s *models.Subscriber) *Message { func (m *Manager) NewMessage(c *models.Campaign, s *models.Subscriber) *Message {
return &Message{ return &Message{
from: c.FromEmail,
to: s.Email,
Campaign: c, Campaign: c,
Subscriber: s, Subscriber: s,
UnsubscribeURL: fmt.Sprintf(m.cfg.UnsubscribeURL, c.UUID, s.UUID),
from: c.FromEmail,
to: s.Email,
unsubURL: fmt.Sprintf(m.cfg.UnsubURL, c.UUID, s.UUID),
} }
} }
@ -413,12 +415,15 @@ func (m *Manager) sendNotif(c *models.Campaign, status, reason string) error {
// compiled campaign templates. // compiled campaign templates.
func (m *Manager) TemplateFuncs(c *models.Campaign) template.FuncMap { func (m *Manager) TemplateFuncs(c *models.Campaign) template.FuncMap {
return template.FuncMap{ return template.FuncMap{
"TrackLink": func(url, campUUID, subUUID string) string { "TrackLink": func(url string, msg *Message) string {
return m.trackLink(url, campUUID, subUUID) return m.trackLink(url, msg.Campaign.UUID, msg.Subscriber.UUID)
}, },
"TrackView": func(campUUID, subUUID string) template.HTML { "TrackView": func(msg *Message) template.HTML {
return template.HTML(fmt.Sprintf(`<img src="%s" alt="" />`, return template.HTML(fmt.Sprintf(`<img src="%s" alt="" />`,
fmt.Sprintf(m.cfg.ViewTrackURL, campUUID, subUUID))) fmt.Sprintf(m.cfg.ViewTrackURL, msg.Campaign.UUID, msg.Subscriber.UUID)))
},
"UnsubscribeURL": func(msg *Message) string {
return msg.unsubURL
}, },
"Date": func(layout string) string { "Date": func(layout string) string {
if layout == "" { if layout == "" {

View File

@ -48,16 +48,27 @@ const (
ContentTpl = "content" ContentTpl = "content"
) )
// regTplFunc represents contains a regular expression for wrapping and
// substituting a Go template function from the user's shorthand to a full
// function call.
type regTplFunc struct {
regExp *regexp.Regexp
replace string
}
// Regular expression for matching {{ Track "http://link.com" }} in the template // Regular expression for matching {{ Track "http://link.com" }} in the template
// and substituting it with {{ Track "http://link.com" .Campaign.UUID .Subscriber.UUID }} // and substituting it with {{ Track "http://link.com" .Campaign.UUID .Subscriber.UUID }}
// before compilation. This string gimmick is to make linking easier for users. // before compilation. This string gimmick is to make linking easier for users.
var ( var regTplFuncs = []regTplFunc{
regexpLinkTag = regexp.MustCompile("{{(\\s+)?TrackLink\\s+?(\"|`)(.+?)(\"|`)(\\s+)?}}") regTplFunc{
regexpLinkTagReplace = `{{ TrackLink "$3" .Campaign.UUID .Subscriber.UUID }}` regExp: regexp.MustCompile("{{(\\s+)?TrackLink\\s+?(\"|`)(.+?)(\"|`)(\\s+)?}}"),
replace: `{{ TrackLink "$3" . }}`,
regexpViewTag = regexp.MustCompile(`{{(\s+)?TrackView(\s+)?}}`) },
regexpViewTagReplace = `{{ TrackView .Campaign.UUID .Subscriber.UUID }}` regTplFunc{
) regExp: regexp.MustCompile(`{{(\s+)?(TrackView|UnsubscribeURL|OptinURL)(\s+)?}}`),
replace: `{{ $2 . }}`,
},
}
// AdminNotifCallback is a callback function that's called // AdminNotifCallback is a callback function that's called
// when a campaign's status changes. // when a campaign's status changes.
@ -264,17 +275,21 @@ func (camps Campaigns) LoadStats(stmt *sqlx.Stmt) error {
// template and sets the resultant template to Campaign.Tpl. // template and sets the resultant template to Campaign.Tpl.
func (c *Campaign) CompileTemplate(f template.FuncMap) error { func (c *Campaign) CompileTemplate(f template.FuncMap) error {
// Compile the base template. // Compile the base template.
t := regexpLinkTag.ReplaceAllString(c.TemplateBody, regexpLinkTagReplace) body := c.TemplateBody
t = regexpViewTag.ReplaceAllString(t, regexpViewTagReplace) for _, r := range regTplFuncs {
baseTPL, err := template.New(BaseTpl).Funcs(f).Parse(t) body = r.regExp.ReplaceAllString(body, r.replace)
}
baseTPL, err := template.New(BaseTpl).Funcs(f).Parse(body)
if err != nil { if err != nil {
return fmt.Errorf("error compiling base template: %v", err) return fmt.Errorf("error compiling base template: %v", err)
} }
// Compile the campaign message. // Compile the campaign message.
t = regexpLinkTag.ReplaceAllString(c.Body, regexpLinkTagReplace) body = c.Body
t = regexpViewTag.ReplaceAllString(t, regexpViewTagReplace) for _, r := range regTplFuncs {
msgTpl, err := template.New(ContentTpl).Funcs(f).Parse(t) body = r.regExp.ReplaceAllString(body, r.replace)
}
msgTpl, err := template.New(ContentTpl).Funcs(f).Parse(body)
if err != nil { if err != nil {
return fmt.Errorf("error compiling message: %v", err) return fmt.Errorf("error compiling message: %v", err)
} }